api-ref: custom API reference (#4770)
* initialized next.js project * finished markdown sections * added operation schema component * change page metadata * eslint fixes * fixes related to deployment * added response schema * resolve max stack issue * support for different property types * added support for property types * added loading for components * added more loading * type fixes * added oneOf type * removed console * fix replace with push * refactored everything * use static content for description * fixes and improvements * added code examples section * fix path name * optimizations * fixed tag navigation * add support for admin and store references * general enhancements * optimizations and fixes * fixes and enhancements * added search bar * loading enhancements * added loading * added code blocks * added margin top * add empty response text * fixed oneOf parameters * added path and query parameters * general fixes * added base path env variable * small fix for arrays * enhancements * design enhancements * general enhancements * fix isRequired * added enum values * enhancements * general fixes * general fixes * changed oas generation script * additions to the introduction section * added copy button for code + other enhancements * fix response code block * fix metadata * formatted store introduction * move sidebar logic to Tags component * added test env variables * fix code block bug * added loading animation * added expand param + loading * enhance operation loading * made responsive + improvements * added loading provider * fixed loading * adjustments for small devices * added sidebar label for endpoints * added feedback component * fixed analytics * general fixes * listen to scroll for other headings * added sample env file * update api ref files + support new fields * fix for external docs link * added new sections * fix last item in sidebar not showing * move docs content to www/docs * change redirect url * revert change * resolve build errors * configure rewrites * changed to environment variable url * revert changing environment variable name * add environment variable for API path * fix links * fix tailwind settings * remove vercel file * reconfigured api route * move api page under api * fix page metadata * fix external link in navigation bar * update api spec * updated api specs * fixed google lint error * add max-height on request samples * add padding before loading * fix for one of name * fix undefined types * general fixes * remove response schema example * redesigned navigation bar * redesigned sidebar * fixed up paddings * added feedback component + report issue * fixed up typography, padding, and general styling * redesigned code blocks * optimization * added error timeout * fixes * added indexing with algolia + fixes * fix errors with algolia script * redesign operation sections * fix heading scroll * design fixes * fix padding * fix padding + scroll issues * fix scroll issues * improve scroll performance * fixes for safari * optimization and fixes * fixes to docs + details animation * padding fixes for code block * added tab animation * fixed incorrect link * added selection styling * fix lint errors * redesigned details component * added detailed feedback form * api reference fixes * fix tabs * upgrade + fixes * updated documentation links * optimizations to sidebar items * fix spacing in sidebar item * optimizations and fixes * fix endpoint path styling * remove margin * final fixes * change margin on small devices * generated OAS * fixes for mobile * added feedback modal * optimize dark mode button * fixed color mode useeffect * minimize dom size * use new style system * radius and spacing design system * design fixes * fix eslint errors * added meta files * change cron schedule * fix docusaurus configurations * added operating system to feedback data * change content directory name * fixes to contribution guidelines * revert renaming content * added api-reference to documentation workflow * fixes for search * added dark mode + fixes * oas fixes * handle bugs * added code examples for clients * changed tooltip text * change authentication to card * change page title based on selected section * redesigned mobile navbar * fix icon colors * fix key colors * fix medusa-js installation command * change external regex in algolia * change changeset * fix padding on mobile * fix hydration error * update depedencies
This commit is contained in:
1 parent
16249ec280
commit
914d773d3a
3270 files changed
+22075
-192064
No files matched your search
@@ -0,0 +1,313 @@
|
||||
---
|
||||
description: 'Learn how to create a fulfillment provider in the Medusa backend. This guide explains the different methods in the fulfillment provider.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Add a Fulfillment Provider
|
||||
|
||||
In this document, you’ll learn how to add a fulfillment provider to a Medusa backend. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to [check out the overview first](../shipping.md).
|
||||
|
||||
## Overview
|
||||
|
||||
A fulfillment provider is the shipping provider used to fulfill orders and deliver them to customers. An example of a fulfillment provider is FedEx.
|
||||
|
||||
By default, a Medusa Backend has a `manual` fulfillment provider which has minimal implementation. It allows you to accept orders and fulfill them manually. However, you can integrate any fulfillment provider into Medusa, and your fulfillment provider can interact with third-party shipping providers.
|
||||
|
||||
Adding a fulfillment provider is as simple as creating one [service](../../../development/services/create-service.mdx) file in `src/services`. A fulfillment provider is essentially a service that extends the `FulfillmentService`. It requires implementing 4 methods:
|
||||
|
||||
1. `getFulfillmentOptions`: used to retrieve available fulfillment options provided by this fulfillment provider.
|
||||
2. `validateOption`: used to validate the shipping option when it’s being created by the admin.
|
||||
3. `validateFulfillmentData`: used to validate the shipping method when the customer chooses a shipping option on checkout.
|
||||
4. `createFulfillment`: used to perform any additional actions when fulfillment is being created for an order.
|
||||
|
||||
Also, the fulfillment provider class should have a static property `identifier`. It is the name that will be used to install and refer to the fulfillment provider throughout Medusa.
|
||||
|
||||
Fulfillment providers are loaded and installed on the backend startup.
|
||||
|
||||
---
|
||||
|
||||
## Create a Fulfillment Provider
|
||||
|
||||
The first step is to create a JavaScript or TypeScript file under `src/services`. For example, create the file `src/services/my-fulfillment.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-fulfillment.ts
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
|
||||
}
|
||||
|
||||
export default MyFulfillmentService
|
||||
```
|
||||
|
||||
Fulfillment provider services must extend the `FulfillmentService` class imported from `medusa-interfaces`.
|
||||
|
||||
:::note
|
||||
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the fulfillment provider, and the name of the class should be the camel case name of the fulfillment provider suffixed with “Service”. You can learn more in the [service documentation](../../../development/services/create-service.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### Identifier
|
||||
|
||||
As mentioned in the overview, fulfillment providers should have a static `identifier` property.
|
||||
|
||||
The `FulfillmentProvider` entity has 2 properties: `identifier` and `is_installed`. The `identifier` property in the class will be used when the fulfillment provider is created in the database.
|
||||
|
||||
The value of this property will also be used to reference the fulfillment provider throughout Medusa. For example, it is used to [add a fulfillment provider](https://docs.medusajs.com/api/admin#regions_postregionsregionfulfillmentproviders) to a region.
|
||||
|
||||
```ts
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
static identifier = "my-fulfillment"
|
||||
}
|
||||
|
||||
export default MyFulfillmentService
|
||||
```
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your fulfillment provider to have access to different services in Medusa through dependency injection. You can access any services you create in Medusa in the first parameter.
|
||||
|
||||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa 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.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
constructor(container, options) {
|
||||
super()
|
||||
// you can access options here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getFulfillmentOptions
|
||||
|
||||
When the admin is creating shipping options available for customers during checkout, they choose one of the fulfillment options provided by underlying fulfillment providers.
|
||||
|
||||
For example, if you’re integrating UPS as a fulfillment provider, you might support two fulfillment options: UPS Express Shipping and UPS Access Point.
|
||||
|
||||
These fulfillment options are defined in the `getFulfillmentOptions` method. This method should return an array of options.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "my-fulfillment",
|
||||
},
|
||||
{
|
||||
id: "my-fulfillment-dynamic",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the admin chooses one of those fulfillment options, the data of the chosen fulfillment option is stored in the `data` property of the shipping option created. This property is used to add any additional data you need to fulfill the order with the third-party provider.
|
||||
|
||||
For that reason, the fulfillment option doesn't have any required structure and can be of any format that works for your integration.
|
||||
|
||||
### validateOption
|
||||
|
||||
Once the admin creates the shipping option, the data will be validated first using this method in the underlying fulfillment provider of that shipping option. This method is called when a `POST` request is sent to [`/admin/shipping-options`](https://docs.medusajs.com/api/admin#shipping-options_getshippingoptions).
|
||||
|
||||
This method accepts the `data` object that is sent in the body of the request. You can use this data to validate the shipping option before it is saved.
|
||||
|
||||
This method returns a boolean. If the result is false, an error is thrown and the shipping option will not be saved.
|
||||
|
||||
For example, you can use this method to ensure that the `id` in the `data` object is correct:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async validateOption(data) {
|
||||
return data.id == "my-fulfillment"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your fulfillment provider does not need to run any validation, you can simply return `true`.
|
||||
|
||||
### validateFulfillmentOption
|
||||
|
||||
When the customer chooses a shipping option on checkout, the shipping option and its data are validated before the shipping method is created.
|
||||
|
||||
`validateFulfillmentOption` is called when a `POST` request is sent to [`/carts/:id/shipping-methods`](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
|
||||
This method accepts three parameters:
|
||||
|
||||
1. The shipping option data.
|
||||
2. The `data` object passed in the body of the request.
|
||||
3. The customer’s cart data.
|
||||
|
||||
You can use these parameters to validate the chosen shipping option. For example, you can check if the `data` object includes all data needed to fulfill the shipment later on.
|
||||
|
||||
If any of the data is invalid, you can throw an error. This error will stop Medusa from creating a shipping method and the error message will be returned as a result to the endpoint.
|
||||
|
||||
If everything is valid, this method must return a value that will be stored in the `data` property of the shipping method to be created. So, make sure the value you return contains everything you need to fulfill the shipment later on.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async validateFulfillmentData(optionData, data, cart) {
|
||||
if (data.id !== "my-fulfillment") {
|
||||
throw new Error("invalid data")
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### createFulfillment
|
||||
|
||||
After an order is placed, it can be fulfilled either manually by the admin or using automation.
|
||||
|
||||
This method gives you access to the fulfillment being created as well as other details in case you need to perform any additional actions with the third-party provider.
|
||||
|
||||
This method accepts four parameters:
|
||||
|
||||
1. The data of the shipping method associated with the order.
|
||||
2. An array of items in the order to be fulfilled. The admin can choose all or some of the items to fulfill.
|
||||
3. The data of the order
|
||||
4. The data of the fulfillment being created.
|
||||
|
||||
You can use the `data` property in the shipping method (first parameter) to access the data specific to the shipping option. This is based on your implementation of previous methods.
|
||||
|
||||
Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
createFulfillment(
|
||||
methodData,
|
||||
fulfillmentItems,
|
||||
fromOrder,
|
||||
fulfillment
|
||||
) {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
This method is also used to create claims and swaps. The fulfillment object has the fields `claim_id`, `swap_id`, and `order_id`. You can check which isn’t null to determine what type of fulfillment is being created.
|
||||
|
||||
:::
|
||||
|
||||
### Useful Methods
|
||||
|
||||
The above-detailed methods are the required methods for every fulfillment provider. However, there are additional methods that you can use in your fulfillment provider to customize it further or add additional features.
|
||||
|
||||
#### canCalculate
|
||||
|
||||
This method validates whether a shipping option is calculated dynamically or flat rate. It is called if the `price_type` of the shipping option being created is set to `calculated`.
|
||||
|
||||
If this method returns `true`, that means that the price should be calculated dynamically. The `amount` property of the shipping option will then be set to `null`. The amount will be created later when the shipping method is created on checkout using the `calculatePrice` method (explained next).
|
||||
|
||||
If the method returns `false`, an error is thrown as it means the selected shipping option can only be chosen if the price type is set to `flat_rate`.
|
||||
|
||||
This method receives as a parameter the `data` object sent with the request that [creates the shipping option.](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions) You can use this data to determine whether the shipping option should be calculated or not. This is useful if the fulfillment provider you are integrating has both flat rate and dynamically priced fulfillment options.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
canCalculate(data) {
|
||||
return data.id === "my-fulfillment-dynamic"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### calculatePrice
|
||||
|
||||
This method is called on checkout when the shipping method is being created if the `price_type` of the selected shipping option is `calculated`.
|
||||
|
||||
This method receives three parameters:
|
||||
|
||||
1. The `data` parameter of the selected shipping option.
|
||||
2. The `data` parameter sent with [the request](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
3. The customer’s cart data.
|
||||
|
||||
If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
calculatePrice() {
|
||||
// leave empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Otherwise, you can use it to calculate the price with a custom logic. For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
calculatePrice(optionData, data, cart) {
|
||||
return cart.items.length * 1000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### createReturn
|
||||
|
||||
Fulfillment providers can also be used to return products. A shipping option can be used for returns if the `is_return` property is `true` or if an admin creates a Return Shipping Option from the settings.
|
||||
|
||||
This method is called when the admin [creates a return request](https://docs.medusajs.com/api/admin#orders_postordersorderreturns) for an order or when the customer [creates a return of their order](https://docs.medusajs.com/api/store#returns_postreturns).
|
||||
|
||||
It gives you access to the return being created in case you need to perform any additional actions with the third-party provider.
|
||||
|
||||
It receives the return created as a parameter. The value it returns is set to the `shipping_data` of the return instance.
|
||||
|
||||
This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
createReturn(returnOrder) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### cancelFulfillment
|
||||
|
||||
This method is called when a fulfillment is cancelled by the admin. This fulfillment can be for an order, a claim, or a swap.
|
||||
|
||||
It gives you access to the fulfillment being canceled in case you need to perform any additional actions with your third-party provider.
|
||||
|
||||
This method receives the fulfillment being cancelled as a parameter.
|
||||
|
||||
This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
cancelFulfillment(fulfillment) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- Example Implementations: [Webshipper plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-webshipper) and the [manual fulfillment plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-manual)
|
||||
@@ -0,0 +1,603 @@
|
||||
---
|
||||
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 Processor
|
||||
|
||||
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
|
||||
|
||||
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.mdx) 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 Processor
|
||||
|
||||
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-processor.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-payment-processor.ts
|
||||
import {
|
||||
AbstractPaymentProcessor,
|
||||
PaymentProcessorContext,
|
||||
PaymentProcessorError,
|
||||
PaymentProcessorSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentProcessor extends AbstractPaymentProcessor {
|
||||
static identifier = "my-payment"
|
||||
|
||||
async capturePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async authorizePayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
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 MyPaymentProcessor
|
||||
```
|
||||
|
||||
Where `MyPaymentProcessor` is the name of your Payment Processor service.
|
||||
|
||||
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 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.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### identifier
|
||||
|
||||
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 Processor is created in the database.
|
||||
|
||||
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](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession).
|
||||
|
||||
The identifier can be retrieved using the `getIdentifier` method, which is defined in `AbstractPaymentProcessor`.
|
||||
|
||||
### constructor
|
||||
|
||||
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 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 {
|
||||
// ...
|
||||
constructor(container, options) {
|
||||
super(container)
|
||||
// you can access options here
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### PaymentProcessorError
|
||||
|
||||
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
|
||||
interface PaymentProcessorError {
|
||||
error: string
|
||||
code?: string
|
||||
detail?: any
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
For example, the Stripe payment processor has the following method to create the error object, which is used within other methods:
|
||||
|
||||
```ts
|
||||
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](https://docs.medusajs.com/api/store#carts_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>
|
||||
}
|
||||
```
|
||||
|
||||
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 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 `initiatePayment`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
PaymentContext,
|
||||
PaymentSessionResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
update_requests,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 Processor this method is used to retrieve the payment intent details from Stripe.
|
||||
|
||||
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.
|
||||
|
||||
An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getPaymentStatus
|
||||
|
||||
This method is used to get the status of a Payment or a Payment Session.
|
||||
|
||||
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 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. 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.
|
||||
3. `requires_more`: The payment requires more actions from the customer. For example, if the customer must complete a 3DS check before the payment is authorized.
|
||||
4. `error`: If an error occurred with the payment.
|
||||
5. `canceled`: If the payment was canceled.
|
||||
|
||||
An example of a minimal implementation of `getPaymentStatus` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
return PaymentSessionStatus.AUTHORIZED
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### updatePayment
|
||||
|
||||
This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a [line item is added to the cart](https://docs.medusajs.com/api/store#carts_postcartscartlineitems) or when a [shipping method is selected](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
|
||||
:::tip
|
||||
|
||||
A line item refers to a product in the cart.
|
||||
|
||||
:::
|
||||
|
||||
It accepts the `data` field of the Payment Session as the first parameter and a context object as a second 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>
|
||||
}
|
||||
```
|
||||
|
||||
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 PaymentProcessorSessionResponse = {
|
||||
update_requests?: {
|
||||
customer_metadata?: Record<string, unknown>
|
||||
}
|
||||
session_data: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
These are the same fields explained in the [initiatePayment](#initiatepayment) section.
|
||||
|
||||
An example of a minimal implementation of `updatePayment`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
PaymentSessionData,
|
||||
Cart,
|
||||
PaymentContext,
|
||||
PaymentSessionResponse,
|
||||
} from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
void |
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
update_requests,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### deletePayment
|
||||
|
||||
This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:
|
||||
|
||||
1. When a request is sent to [delete the Payment Session](https://docs.medusajs.com/api/store#carts_deletecartscartpaymentsessionssession).
|
||||
2. When the [Payment Session is refreshed](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessionssession). The Payment Session is deleted so that a newer one is initialized instead.
|
||||
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 `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.
|
||||
|
||||
An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required:
|
||||
|
||||
```ts
|
||||
import { PaymentSession } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return paymentSessionData
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### authorizePayment
|
||||
|
||||
This method is used to authorize payment using the Payment Session of an order. This is called when the [cart is completed](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) and before the order is created.
|
||||
|
||||
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.
|
||||
|
||||
Once the payment is authorized successfully and the Payment Session status is set to `authorized`, the order can then be placed.
|
||||
|
||||
If the payment authorization fails, then an error will be thrown and the order will not be created.
|
||||
|
||||
:::note
|
||||
|
||||
The payment authorization status is determined using the `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 `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 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.
|
||||
|
||||
An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider:
|
||||
|
||||
```ts
|
||||
import {
|
||||
Data,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
PaymentSessionData,
|
||||
} from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async authorizePayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
return {
|
||||
status: PaymentSessionStatus.AUTHORIZED,
|
||||
data: {
|
||||
id: "test",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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, or when the [Capture Payment](https://docs.medusajs.com/api/admin#payments_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 `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.
|
||||
|
||||
An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async capturePayment(payment: Payment): Promise<Data> {
|
||||
return {
|
||||
status: "captured",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### refundPayment
|
||||
|
||||
This method is used to refund an order’s payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total order amount or part of it.
|
||||
|
||||
This method is also used for refunding payments of a swap or a claim of an order, or when the [Refund Payment](https://docs.medusajs.com/api/admin#payments_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 `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.
|
||||
|
||||
An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async refundPayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### cancelPayment
|
||||
|
||||
This method is used to cancel an order’s payment. This method is typically triggered by one of the following situations:
|
||||
|
||||
1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled.
|
||||
2. If the store operator cancels the order from the admin.
|
||||
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 `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:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- Implementation Examples: [Stripe](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-paypal) Payment Processors.
|
||||
- [Implement checkout flow on the storefront](../storefront/implement-checkout-flow.mdx).
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
description: 'Learn how to override the cart completion strategy to implement your custom cart completion strategy.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override Cart Completion Strategy
|
||||
|
||||
In this document, you’ll learn how to override the cart completion strategy.
|
||||
|
||||
:::note
|
||||
|
||||
This guide only explains how to override the cart completion strategy. It’s highly recommended to first understand how Medusa implements the cart completion strategy as explained [here](../cart.md#cart-completion-process).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Strategy Class
|
||||
|
||||
Create a TypeScript or JavaScript file in `src/strategies` of your Medusa backend project with a class that extends the `AbstractCartCompletionStrategy` class:
|
||||
|
||||
```ts title=src/strategies/cart-completion.ts
|
||||
import {
|
||||
AbstractCartCompletionStrategy,
|
||||
CartCompletionResponse,
|
||||
IdempotencyKey } from "@medusajs/medusa"
|
||||
import {
|
||||
RequestContext,
|
||||
} from "@medusajs/medusa/dist/types/request"
|
||||
|
||||
class CartCompletionStrategy
|
||||
extends AbstractCartCompletionStrategy {
|
||||
|
||||
complete(
|
||||
cartId: string,
|
||||
idempotencyKey: IdempotencyKey,
|
||||
context: RequestContext
|
||||
): Promise<CartCompletionResponse> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CartCompletionStrategy
|
||||
```
|
||||
|
||||
The class includes the `complete` method defined as abstract in `AbstractCartCompletionStrategy`. At the moment, the method only throws an error.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title=src/strategies/cart-completion.ts
|
||||
// ...
|
||||
import { IdempotencyKeyService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
idempotencyKeyService: IdempotencyKeyService
|
||||
}
|
||||
|
||||
class CartCompletionStrategy
|
||||
extends AbstractCartCompletionStrategy {
|
||||
|
||||
protected readonly idempotencyKeyService_:
|
||||
IdempotencyKeyService
|
||||
|
||||
constructor(
|
||||
{ idempotencyKeyService }: InjectedDependencies
|
||||
) {
|
||||
super(arguments[0])
|
||||
this.idempotencyKeyService_ = idempotencyKeyService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CartCompletionStrategy
|
||||
```
|
||||
|
||||
In the above example, you inject the `IdempotencyKeyService` in the constructor. This allows you to use the `IdempotencyKeyService` within your class.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement the complete Method
|
||||
|
||||
The cart completion strategy is required to implement a single method: the `complete` method. This method is used in the [Complete Cart endpoint](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) to handle the logic of completing the cart.
|
||||
|
||||
The method accepts three parameters:
|
||||
|
||||
- `cartId`: the first parameter of the method, which is a string indicating the ID of the cart to complete.
|
||||
- `idempotencyKey`: the second parameter of the method, which is an instance of the `IdempotencyKey` entity. The idempotency key is retrieved based on the idempotency key passed in the header of the request, and it’s used to determine the current point reached in the checkout flow to avoid inconsistencies on interruptions. You can learn more about the idempotency key [here](../cart.md#idempotency-key). You can also learn how to use it within your strategy by following [this guide](../../../development/idempotency-key/use-service.md)
|
||||
- `context`: the third parameter of the method, which is an object that holds a single property `ip`. `ip` is a string indicating the IP of the customer.
|
||||
|
||||
The completion strategy is expected to return an object with the following properties:
|
||||
|
||||
- `response_code`: a number indicating the response code.
|
||||
- `response_body`: an object that will be returned to the client.
|
||||
|
||||
You can refer to this guide to learn how the cart conceptual guide is implemented in the Medusa backend. This can help you understand how details such as inventory, taxes, and more are handled.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, try out your strategy using the Complete Cart endpoint. You should see the logic you implemented used for completing the cart.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to implement cart functionalities in the storefront](../storefront/implement-cart.mdx)
|
||||
- [How to implement checkout flow in the storefront](../storefront/implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
description: 'Learn about the Cart entity, its relation to other entities, and how the cart completion process is implemented.'
|
||||
---
|
||||
|
||||
# Cart Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the Cart entity, its relation to other entities, and how the cart completion strategy is implemented.
|
||||
|
||||
## Overview
|
||||
|
||||
The cart allows customers to go from browsing products to completing an order. Many actions can be performed on a cart, like adding line items, applying discounts, creating shipping methods, and more before eventually completing it and creating an [Order](../orders/orders.md).
|
||||
|
||||
---
|
||||
|
||||
## Cart Entity Overview
|
||||
|
||||
A cart is represented by the `Cart` entity. Some of the `Cart` entity’s attributes include:
|
||||
|
||||
- `email`: The email the cart is associated with.
|
||||
- `type`: A string indicating what the cart is used for. Its value can be:
|
||||
- `default` if the cart is used to place an order.
|
||||
- `swap` if the cart is used to create and register a swap
|
||||
- `draft_order` if the cart is used to create and complete a draft order.
|
||||
- `payment_link` if the cart is used for a payment link.
|
||||
- `claim` if the cart is used to create a claim.
|
||||
- `completed_at`: the date the cart was completed. A completed cart means that it has been used for its main purpose. For example, if the cart is used to place an order, then a completed cart means that the order was placed.
|
||||
- `payment_authorized_at`: the date a payment was authorized.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full Cart entity in the entities reference](../../references/entities/classes/Cart.md).
|
||||
|
||||
---
|
||||
|
||||
## Cart Totals Calculation
|
||||
|
||||
By default, the `Cart` entity doesn’t hold any details regarding the totals. These are computed and added to the cart instance using the `CartService`'s [decorateTotals method](../../references/services/classes/CartService.md#decoratetotals). There's also a dedicated method in the `CartService`, [retrieveWithTotals](../../references/services/classes/CartService.md#retrieveWithTotals), attaching the totals automatically. It is recommended to use this method by default when you need to retrieve the cart.
|
||||
|
||||
The cart’s totals are calculated based on the content and context of the cart. This includes the selected region, whether tax-inclusive pricing is enabled, the chosen shipping methods, and more.
|
||||
|
||||
The calculated cart’s totals include:
|
||||
|
||||
- `shipping_total`: The total of the chosen shipping methods, with taxes.
|
||||
- `shipping_tax_total`: The applied taxes on the shipping total.
|
||||
- `discount_total`: The total of the applied discounts.
|
||||
- `raw_discount_total`: The total of the applied discounts without rounding.
|
||||
- `item_tax_total`: The total applied taxes on the cart’s items.
|
||||
- `tax_total`: The total taxes applied (the sum of `shipping_tax_total` and `item_tax_total`).
|
||||
- `gift_card_total`: The total gift card amount applied on the cart. If there are any taxes applied on the gift cards, they’re deducted from the total.
|
||||
- `gift_card_tax_total`: The total taxes applied on the cart’s gift cards.
|
||||
- `subtotal`: The total of the items without taxes or discounts.
|
||||
- `total`: The overall total of the cart.
|
||||
|
||||
:::note
|
||||
|
||||
If you have tax-inclusive pricing enabled, you can learn about other available total fields [here](../taxes/inclusive-pricing.md#retrieving-tax-amounts).
|
||||
|
||||
:::
|
||||
|
||||
The cart’s totals are retrieved by default in all the [cart’s store APIs](https://docs.medusajs.com/api/store#carts).
|
||||
|
||||
---
|
||||
|
||||
## Cart Completion
|
||||
|
||||
The cart completion functionality is implemented inside the strategy `cartCompletionStrategy`. This allows you to customize how the process is implemented in your store.
|
||||
|
||||
You can initiate the cart completion process by sending a request to the [Complete Cart endpoint](https://docs.medusajs.com/api/store#carts_postcartscartcomplete).
|
||||
|
||||
### Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a cart. It is generated when the cart completion process is started and can be used to retry cart completion safely if an error occurs. The idempotency key is stored in the Cart entity under the attribute `idempotency_key`.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
### Cart Completion Process
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn how to override the cart completion strategy [here](./backend/cart-completion-strategy.md).
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
The process is implemented as follows:
|
||||
|
||||
1. When the idempotency key’s recovery point is set to `started`, the tax lines are created for the items in the cart. This is done using the `CartService`'s [createTaxLines method](../../references/services/classes/CartService.md#createtaxlines). If that is completed with no errors, the recovery point is set to `tax_lines_created` and the process continues.
|
||||
2. When the idempotency key’s recovery point is set to `tax_lines_created`, the payment is authorized using the `CartService`'s method [authorizePayment](../../references/services/classes/CartService.md#authorizepayment). If the payment requires more action or is pending authorization, then the tax lines that were created in the previous steps are deleted and the cart completion process is terminated. Once the payment is authorized, the process can be restarted.
|
||||
3. When the idempotency key’s recovery point is set to `payment_authorized`, tax lines are created again the same way as the first step. Then, the inventory of each of the items in the cart is confirmed using the `ProductVariantInventoryService`'s method [confirmInventory](../../references/services/classes/ProductVariantInventoryService.md#confirminventory). If an item is in stock, the quantity is reserved using the `ProductVariantInventoryService`'s method [reserveQuantity](../../references/services/classes/ProductVariantInventoryService.md#reservequantity). If an item is out of stock, any item reservations that were created are deleted, the payment is canceled, and an error is thrown, terminating the cart completion process. If all item quantities are confirmed to be available:
|
||||
1. If the cart belongs to a swap (the `type` attribute is set to `swap`), the swap is registered as completed using the `SwapService`'s [registerCartCompletion method](../../references/services/classes/SwapService.md#registercartcompletion) and the inventory item reservations are removed using the Inventory module. The process ends successfully here for a swap.
|
||||
2. If the cart belongs to an order, the order is created using the `OrderService`'s method [createFromCart](../../references/services/classes/OrderService.md#createfromcart). The order is then retrieved and sent in the response.
|
||||
4. Once the process detailed above is done, the idempotency key’s recovery point is set to `finished`.
|
||||
|
||||
---
|
||||
|
||||
## Cart’s Relation to Other Entities
|
||||
|
||||
### Region
|
||||
|
||||
A cart is associated with a [region](../regions-and-currencies/regions.md), which is represented by the `Region` entity. This ensures the prices, discounts, and other conditions that depend on the region are accurate for a customer.
|
||||
|
||||
The region’s ID is stored in the `Cart` entity under the attribute `region_id`. You can also access the region by expanding the `region` relation and accessing `cart.region`.
|
||||
|
||||
### Sales Channel
|
||||
|
||||
A [sales channel](../sales-channels/sales-channels.md) indicates different selling points a business offers its products in. It is represented by the `SalesChannel` entity.
|
||||
|
||||
A cart can be associated with a sales channel. When adding products to the cart, it’s important that the cart and the product belong to the same sales channel.
|
||||
|
||||
The sales channel’s ID is stored in the `sales_channel_id` attribute of the `Cart` entity. You can also access the sales channel by expanding the `sales_channel` relation and accessing `cart.sales_channel`.
|
||||
|
||||
### Address
|
||||
|
||||
A cart can have a shipping and a billing address, both represented by the `Address` entity.
|
||||
|
||||
The billing address’s ID is stored in the `billing_address_id` attribute of the `Cart` entity. You can also access the billing address by expanding the `billing_address` relation and accessing `cart.billing_address`.
|
||||
|
||||
The shipping address’s ID is stored in the `shipping_address_id` attribute of the `Cart` entity. You can also access the shipping address by expanding the `shipping_address` relation and accessing `cart.shipping_address`.
|
||||
|
||||
### LineItem
|
||||
|
||||
Products added to the cart are represented by the `LineItem` entity. You can access the cart’s items by expanding the `items` relation and accessing `cart.items`.
|
||||
|
||||
### Discount
|
||||
|
||||
[Discounts](../discounts/discounts.md) can be added to the cart to deduct the cart’s total. A discount is represented by the `Discount` entity.
|
||||
|
||||
You can access the discounts applied on a cart by expanding the `discounts` relation and accessing `cart.discounts`.
|
||||
|
||||
### GiftCard
|
||||
|
||||
[Gift cards](../gift-cards/gift-cards.md) can be applied on a cart to benefit from a pre-paid balance during payment. A gift card is represented by the `GiftCard` entity.
|
||||
|
||||
You can access the gift cards applied on a cart by expanding the `gift_cards` relation and accessing `cart.gift_cards`.
|
||||
|
||||
### Customer
|
||||
|
||||
A cart can be associated with either a logged-in [customer](../customers/customers.md) or a guest customer, both represented by the `Customer` entity.
|
||||
|
||||
You can access the customer by expanding the `customer` relation and accessing `cart.customer`.
|
||||
|
||||
### PaymentSession
|
||||
|
||||
A payment session is an available payment method that the customer can use during the checkout process. It is represented by the `PaymentSession` entity.
|
||||
|
||||
A cart can have multiple payment sessions that the customer can choose from. You can access the payment sessions by expanding the `payment_sessions` relation and accessing `cart.payment_sessions`.
|
||||
|
||||
You can also access the currently selected payment session through `cart.payment_session`.
|
||||
|
||||
### Payment
|
||||
|
||||
A payment is the authorized amount required to complete the cart. It is represented by the `Payment` entity.
|
||||
|
||||
The ID of the payment is stored in the `payment_id` attribute of the `Cart` entity. You can also access the payment by expanding the `payment` relation and accessing `cart.payment`.
|
||||
|
||||
### ShippingMethod
|
||||
|
||||
A shipping method indicates the chosen shipping method used to fulfill the order created later. It is represented by the `ShippingMethod` entity.
|
||||
|
||||
A cart can have more than one shipping method. You can access the shipping methods by expanding the `shipping_methods` relation and accessing `cart.shipping_methods`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to override the cart completion strategy](./backend/cart-completion-strategy.md)
|
||||
- [How to implement the cart functionality in a storefront](./storefront/implement-cart.mdx)
|
||||
- [How to implement the checkout flow in a storefront](./storefront/implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
description: "A cart is a virtual shopping basket that customers can use to pick products they want to purchase. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Carts and Checkout
|
||||
|
||||
A cart is a virtual shopping basket that customers can use to pick products they want to purchase. Checkout is the process of the customer placing an order. This overview introduces the available features related to carts and checkout.
|
||||
|
||||
## Features
|
||||
|
||||
### Cart Management
|
||||
|
||||
Customers can manage their cart including adding, updating, and removing items from the cart.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/storefront/implement-cart',
|
||||
label: 'Storefront: Implement Cart',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement cart functionality in a storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#carts',
|
||||
label: 'Storefront APIs: Carts',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Carts.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Shipping and Payment
|
||||
|
||||
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 processors during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx).
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-fulfillment-provider',
|
||||
label: 'Backend: Create Fulfillment Provider',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a fulfillment provider in the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Backend: Create Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a payment processor in the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/regions/providers',
|
||||
label: 'User Guide: Manage Providers',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage available providers using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Checkout Flow
|
||||
|
||||
Developers can implement a seamless checkout flow that include steps related to shipping and payment, tax calculation, and more.
|
||||
|
||||
Customers can place orders using the checkout flow.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/cart-completion-strategy',
|
||||
label: 'Backend: Override Cart Completion',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to override the cart completion strategy.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/storefront/implement-checkout-flow',
|
||||
label: 'Storefront: Implement Checkout',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement the checkout flow in a storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#carts',
|
||||
label: 'Storefront APIs: Cart',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Carts related to checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how cart-related entities are build, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/cart',
|
||||
label: 'Architecture: Cart',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the cart completion process.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/shipping',
|
||||
label: 'Architecture: Shipping',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Shipping architecture.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/payment',
|
||||
label: 'Architecture: Payment',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Payment architecture.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Carts and Checkout’s relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Products',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Customers can add products to cart and purchase them.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/overview',
|
||||
label: 'Taxes',
|
||||
customProps: {
|
||||
icon: Icons['cash-solid'],
|
||||
description: 'Taxes can be calculated for a cart either automatically or manually.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/overview',
|
||||
label: 'Discounts',
|
||||
customProps: {
|
||||
icon: Icons['currency-dollar-solid'],
|
||||
description: 'Discounts can be applied on a cart to reduce its total.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/overview',
|
||||
label: 'Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['gift-solid'],
|
||||
description: 'Purchased gift cards can be applied during checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
description: 'Learn about the payment architecture in the Medusa backend. The payment architecture refers to all operations in the ecommerce store related to processing payments.'
|
||||
---
|
||||
|
||||
# Payment Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the payment architecture in Medusa, specifically its 3 main components and the idempotency key.
|
||||
|
||||
## Introduction
|
||||
|
||||
The payment architecture refers to all operations in 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 Processor, Payment Session, and 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 Processor
|
||||
|
||||
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 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 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.
|
||||
|
||||
:::tip
|
||||
|
||||
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.
|
||||
|
||||
:::
|
||||
|
||||
### How Payment Processor is Created
|
||||
|
||||
A Payment Processor is essentially a Medusa [service](../../development/services/create-service.mdx) 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](https://docs.medusajs.com/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 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` 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 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.
|
||||
|
||||
After the checkout process is completed and the Payment Session has been authorized successfully, a Payment instance will be created to be associated with the customer’s order and will be used for further actions related to that order.
|
||||
|
||||
### How 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 Processor available in that region.
|
||||
|
||||
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 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` 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 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 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 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.
|
||||
|
||||
---
|
||||
|
||||
## Payment
|
||||
|
||||
A Payment is used to represent the amount authorized for a customer’s purchase. It is associated with the order placed by the customer and will be used after that for all operations related to the order’s payment such as capturing or refunding the payment.
|
||||
|
||||
Payments are generally created using data from the Payment Session and it holds any data that can be necessary to perform later payment operations.
|
||||
|
||||
### How Payment is Created
|
||||
|
||||
Once the customer completes their purchase and the payment has been authorized, a Payment instance will be created from the Payment Session. The Payment is associated first with the cart and then with the order once it’s created and placed.
|
||||
|
||||
When the store operator then chooses to capture the order from the Medusa Admin, the Payment is used by the Payment 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 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.
|
||||
|
||||
Similar to `PaymentSession`, `Payment` has a `data` attribute which is an object that holds any data required to perform further actions with the payment such as capturing the payment.
|
||||
|
||||
`Payment` also holds attributes like `amount` which is the amount authorized for payment, and `amount_refunded` which is the amount refunded from the original amount if a refund has been initiated.
|
||||
|
||||
Additionally, `Payment` has the `captured_at` date-time attribute which is filled when the payment has been captured, and a `canceled_at` date-time attribute which is filled when the order has been canceled.
|
||||
|
||||
---
|
||||
|
||||
## Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a cart. It is generated at the last step of checkout before authorization of the payment is attempted and used in the request and response header.
|
||||
|
||||
If the request is interrupted for any reason or the payment fails, the client can retry completing the check out using the Idempotency Key, and the flow will continue from the last stored step. This prevents any payment issues from occurring with the customers and allows for secure retries of failed payments or interrupted connections.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Available Payment Plugins](../../plugins/payment/index.mdx)
|
||||
- [Create a Payment Processor](./backend/add-payment-provider.md)
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
description: 'Learn how the shipping architecture is implemented in the Medusa backend. This includes an overview of what the Fulfillment Provider, Shipping Profile, Shipping Option, and Shipping Methods.'
|
||||
---
|
||||
|
||||
# Shipping Architecture Overview
|
||||
|
||||
This document gives an overview of the shipping architecture and its four most important components.
|
||||
|
||||
## Introduction
|
||||
|
||||
In Medusa, the Shipping architecture relies on 4 components: **Fulfillment Provider**, **Shipping Profiles**, **Shipping Options**, and **Shipping Methods**.
|
||||
|
||||
The distinction between the four is important. It has been carefully planned and put together to support all the different ecommerce use cases and shipping providers that can be integrated.
|
||||
|
||||
It’s also constructed to support multiple regions, provide different shipment configurations and options for different product types, provide promotional shipments for your customers, and much more.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- **Fulfillment Provider:** Fulfillment providers are plugins or [Services](../../development/services/create-service.mdx) used to ship the products to your customers, whether physically or virtually. An example of a fulfillment provider would be FedEx.
|
||||
- **Shipping Profiles:** created by the admin. They are used to group products that should be shipped in a different manner than the default. Shipping profiles can have multiple shipping options.
|
||||
- **Shipping Options:** created by the admin and belong to a shipping profile. They are specific to certain regions and can have cart conditions. They use an underlying fulfillment provider. Once a customer checks out, they can choose the shipping option that’s available and most relevant to them.
|
||||
- **Shipping Method:** created when the customer chooses a shipping option on checkout. The shipping method is basically a copy of the shipping option, but with values specific to the customer and the cart it’s associated with. When the order is placed, the shipping method will then be associated with the order and fulfilled based on the integration with the fulfillment provider.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Provider
|
||||
|
||||
A Fulfillment Provider in Medusa is a method to handle shipping products in selected regions. It is not associated with a cart, customer, or order in particular.
|
||||
|
||||
It provides the necessary implementation to create Fulfillments for orders and ship items to customers. They can also be used for order returns and swaps.
|
||||
|
||||
Fulfillment Providers can be integrated with third-party services that handle the actual shipment of products. An example of a Fulfillment Provider is FedEx.
|
||||
|
||||
Fulfillment Providers can also be related to a custom way of handling fulfillment operations. An example of that is Medusa’s [manual fulfillment provider plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-manual) which provides a minimal implementation of a fulfillment provider and allows store operators to manually handle order fulfillments.
|
||||
|
||||
### How Fulfillment Provider is Created
|
||||
|
||||
A Fulfillment Provider is essentially a Medusa [Service](../../development/services/create-service.mdx) with a unique identifier, and it extends the `FulfillmentService` provided by the `medusa-interfaces` package. 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.
|
||||
|
||||
As a developer, you will mainly work with the Fulfillment Provider when integrating a fulfillment method in Medusa.
|
||||
|
||||
When you run your Medusa backend, the Fulfillment Provider will be registered on your backend if it hasn’t been already.
|
||||
|
||||
Once the Fulfillment Provider is added to the backend, the store operator will be able to associate on the [Medusa Admin](../../development/backend/install.mdx) the Fulfillment Provider with shipping options.
|
||||
|
||||
### FulfillmentProvider Entity Overview
|
||||
|
||||
The [`FulfillmentProvider`](../../references/entities/classes/FulfillmentProvider.md) entity only has 2 attributes: `is_installed` to indicate if the fulfillment provider is installed and its value is a boolean; and `id` which is the unique identifier that you define in the Fulfillment Provider Service.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Profile
|
||||
|
||||
Shipping profiles are the highest in the hierarchy in the shipping architecture.
|
||||
|
||||
Shipping profiles are created by the admin. The admin can specify the name of the shipping profile which will be a name that the customer can see.
|
||||
|
||||
A shipping profile is not associated with any fulfillment providers. It has multiple shipping options that can be associated with different providers.
|
||||
|
||||
### Purpose of Shipping Profile
|
||||
|
||||
Shipping profiles are used to group products that can be shipped in the same manner.
|
||||
|
||||
The default shipping profile is one that groups all of your store’s products. You also get a shipping profile that’s specific to gift cards. This is because, generally speaking, all products would be delivered similarly, whereas gift cards would be delivered in different behavior.
|
||||
|
||||
Although this might be the general case, there are still some use cases where you will have a set of products that should be shipped differently than others.
|
||||
|
||||
For example, shipping heavy items might be more expensive than others, which would enforce different price rates. In that case, you can create a new shipping profile that groups together heavy products. This would allow you to give these products more suitable price rates when creating their shipping options.
|
||||
|
||||
### ShippingProfile Entity Overview
|
||||
|
||||
The [`ShippingProfile`](../../references/entities/classes/ShippingProfile.md) entity can have a set of `Product` instances. These would be the products the shipping profile is providing shipping options for.
|
||||
|
||||
The `ShippingProfile` has a `type` attribute that can be `default`, `gift_card`, or `custom`.
|
||||
|
||||
The `ShippingProfile` entity also has an array of `ShippingOption` instances.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Option
|
||||
|
||||
After the admin adds a shipping profile, they can add shipping options that belong to that shipping profile from the admin dashboard.
|
||||
|
||||
Shipping options have a set of conditions like the region they’re available in or cart-specific conditions. For example, if your company operates in the United States as well as Germany, you might use a different shipping option for each of the two countries.
|
||||
|
||||
Among the configurations that the admin has to set when creating a shipping option is specifying the fulfillment provider it uses. This means that when you create a plugin for a fulfillment provider, that provider needs to be chosen as the fulfillment provider of a shipping option to be used in the store.
|
||||
|
||||
Shipping options are only shown to a customer during checkout if their cart satisfies the option’s conditions. Also, as they belong to a shipping profile, they’re only shown when products that belong to the same shipping profile are in the cart.
|
||||
|
||||
### Purpose of Shipping Option
|
||||
|
||||
The first purpose that a shipping option has is showing the customer during checkout what shipping options are available for them.
|
||||
|
||||
Then, once the customer chooses a shipping option, that shipping option is used to create a shipping method with details specific to the customer and their cart. Then, the shipping method is associated with the cart, and the shipping option remains untouched.
|
||||
|
||||
Think of a shipping option as a template defined by the admin that indicates what data and values the shipping method should have when it’s chosen by the customer during checkout.
|
||||
|
||||
### ShippingOption Entity Overview
|
||||
|
||||
The [`ShippingOption`](../../references/entities/classes/ShippingOption.md) entity belongs to the `ShippingProfile` entity.
|
||||
|
||||
The `ShippingOption` entity also belongs to a `FulfillmentProvider`. This can be either a custom third-party provider or one of Medusa’s default fulfillment providers.
|
||||
|
||||
It has the `price_type` attribute to indicate whether the shipping option’s rate is `calculated` by the provider or a fixed `flat_rate` price. It also has the `amount` attribute to set an amount for the shipping option if the `price_type` is `flat_rate`.
|
||||
|
||||
`ShippingOption` also belongs to a `Region`, which resembles one or more countries. This defines where the shipping option is available.
|
||||
|
||||
`ShippingOption` has a set of `ShippingOptionRequirement` instances. The `ShippingOptionRequirement` entity allows defining cart rules which determine whether the shipping option will be available or not for a customer during checkout. For example, you can set a minimum subtotal amount for a shipping option to be available for a customer’s cart.
|
||||
|
||||
The `is_return` attribute is used to indicate whether the shipping option is used for shipping orders or returning orders. Shipping options can only be used for one or the other.
|
||||
|
||||
The `data` attribute is used to specify any data necessary for fulfilling the shipment based on the underlying fulfillment provider. When you integrate a fulfillment provider, you can check in that provider’s documentation for any data necessary when creating a new shipment.
|
||||
|
||||
The `data` attribute does not have any specific format. It’s up to you to choose whatever data is included here.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Method
|
||||
|
||||
Unlike the previous two components, a shipping method is not created by the admin. It’s created when a `POST` request is sent to `/store/carts/:id/shipping-methods` after the customer chooses a shipping option.
|
||||
|
||||
The shipping method will be created based on the chosen shipping option and it’ll be associated with the customer’s cart. Then, when the order is placed, the shipping method is associated with the order.
|
||||
|
||||
A shipping method can be fulfilled automatically or manually through the admin dashboard. This is based on the fulfillment provider associated with the shipping option the shipping method is based on.
|
||||
|
||||
### Shipping Method Purpose
|
||||
|
||||
It’s important to understand the distinction between shipping methods and shipping options. Shipping options are templates created by the admin to indicate what shipping options should be shown to a customer. This provides customization capabilities in a store, as an admin is free to specify configurations for that option such as what fulfillment provider it uses or what are its rates.
|
||||
|
||||
When handling the order and fulfilling it, you, as a developer, will be mostly interacting with the shipping method.
|
||||
|
||||
This separation allows for developers to implement the custom integration with third-party fulfillment providers as necessary while also ensuring that the admin has full control of their store.
|
||||
|
||||
### ShippingMethod Entity Overview
|
||||
|
||||
A lot of the shipping method’s attributes are similar to the shipping option’s attribute.
|
||||
|
||||
The [`ShippingMethod`](../../references/entities/classes/ShippingMethod.md) entity belongs to a `ShippingOption`.
|
||||
|
||||
Similar to the `data` attribute explained for the `ShippingOption` entity, a `ShippingMethod` has a similar `data` attribute that includes all the data to be sent to the fulfillment provider when fulfilling the order.
|
||||
|
||||
The `ShippingMethod` belongs to a `Cart`. This is the cart the customer is checking out with.
|
||||
|
||||
The `ShippingMethod` also belongs to the `Order` entity. This association is accomplished when the order is placed.
|
||||
|
||||
The `ShippingMethod` instance holds a `price` attribute, which will either be the flat rate price or the calculated price.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Fulfillment Provider](./backend/add-fulfillment-provider.md)
|
||||
- [Available shipping plugins](https://github.com/medusajs/medusa/tree/master/packages)
|
||||
@@ -0,0 +1,654 @@
|
||||
---
|
||||
description: 'Learn how to implement the cart functionality in your storefront using the REST APIs. This includes creating a cart, updating a cart, adding products to the cart, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Add Cart Functionality
|
||||
|
||||
This document guides you through how you can add cart-related functionalities to your storefront. That includes creating and updating a cart and managing items in the cart.
|
||||
|
||||
## Overview
|
||||
|
||||
Carts are necessary for ecommerce platforms to allow customers to buy products. Each customer, whether logged in or as a guest, should have a cart associated with them. The customer can then add products to the cart.
|
||||
|
||||
This document helps you understand how to add the cart functionality to your storefront. This is helpful if you’re creating the storefront from scratch, or you want to understand how the process generally works in Medusa’s starter storefronts.
|
||||
|
||||
:::note
|
||||
|
||||
This document does not cover implementing the checkout flow. You can refer to [this documentation instead to learn how to implement the checkout flow](./implement-checkout-flow.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
---
|
||||
|
||||
## Create a Cart
|
||||
|
||||
You can create a cart with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.create()
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{}, // create an empty cart
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request does not require any parameters. It returns the created cart in the response.
|
||||
|
||||
The cart by default will have a random region assigned to it. You can specify the cart's region by passing in the request's body a `region_id` parameter:
|
||||
|
||||
Otherwise, you can assign it a specific region during creation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.create({
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{
|
||||
region_id,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Check out the [API Reference](https://docs.medusajs.com/api/store#carts_postcart) for a full list of available request body parameters.
|
||||
|
||||
:::note
|
||||
|
||||
The region a cart is associated with determines the currency the cart uses, the tax, payment, and fulfillment providers, and other details and options. So, make sure you use the correct region for a cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Cart
|
||||
|
||||
Notice that in the previous code snippets, you set the cart’s ID in the local storage. This is helpful to persist the customer’s cart even when they leave the website and come back later.
|
||||
|
||||
You can retrieve the cart at any given point using its ID with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
medusa.carts.retrieve(id)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React" default>
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart } = useCart()
|
||||
|
||||
return (
|
||||
<div>
|
||||
Items in Cart: {cart?.items.length || 0}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
fetch(`<BACKEND_URLL>/store/carts/${id}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter and returns the cart of that ID.
|
||||
|
||||
You can run this code snippet every time the storefront is opened. If a customer has a cart ID stored in their local storage, it’s loaded from the backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Make sure to remove the ID from the local storage after the customer places an order with this cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update a Cart
|
||||
|
||||
A cart has different data associated with it including the region, email, address, customer, and more.
|
||||
|
||||
You can use the following snippet to update any of the cart’s data:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeRegionId = (region_id: string) => {
|
||||
updateCart.mutate({
|
||||
region_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In its body, you can pass any data you want to update in the cart such as the region.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
Check out the full list of available request body parameters in the [API Reference](https://docs.medusajs.com/api/store#carts_postcart).
|
||||
|
||||
### Associate a Logged-In Customer with the Cart
|
||||
|
||||
A customer might add items to their cart, then creates an account or log in. In that case, you should ensure that the cart is associated with the logged-in customer moving forward.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
customer_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeCustomerId = (customer_id: string) => {
|
||||
updateCart.mutate({
|
||||
customer_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This updates the `customer_id` associated with the cart to make sure it belongs to a specific customer.
|
||||
|
||||
### Associate Guest Customers with a Cart using Email
|
||||
|
||||
In case the customer doesn't want to use their own account, you must at least associate an email address with the cart before completing the cart and placing the order.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeEmail = (email: string) => {
|
||||
updateCart.mutate({
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Line Item to the Cart
|
||||
|
||||
To create a line item of a product and add it to a cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.lineItems.create(cartId, {
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const createLineItem = useCreateLineItem(cart_id)
|
||||
|
||||
const handleAddItem = () => {
|
||||
createLineItem.mutate({
|
||||
variant_id,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In the body, it's required to send the ID of the product variant you want to add to the cart and its quantity.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
This adds a new line item to the cart. Line items can be accessed using `cart.items` which is an array that holds all line items in the cart. You can learn more about what properties line items have in the [API reference](https://docs.medusajs.com/api/store#carts_postcartscartlineitems).
|
||||
|
||||
:::note
|
||||
|
||||
If you’re using Sales Channels, make sure that the cart and the product belong to the same sales channel. You can update the cart’s sales channel by [updating the cart](#update-a-cart).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update Line Item in the Cart
|
||||
|
||||
To update a line item's quantity in the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.update(cartId, lineItemId, {
|
||||
quantity: 3,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const updateLineItem = useUpdateLineItem(cart_id)
|
||||
|
||||
const handleUpdateItem = () => {
|
||||
updateLineItem.mutate({
|
||||
lineId,
|
||||
quantity: 3,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 3,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters. In the body, it accepts the quantity of the line item.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Line Item from the Cart
|
||||
|
||||
To delete a line item from the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.delete(cartId, lineItemId)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useDeleteLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const deleteLineItem = useDeleteLineItem(cart_id)
|
||||
|
||||
const handleDeleteItem = () => {
|
||||
deleteLineItem.mutate({
|
||||
lineId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement the checkout flow in your storefront](./implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,597 @@
|
||||
---
|
||||
description: 'Learn how to implement the checkout flow in your storefront using the REST APIs. This includes adding steps related to shipping and payment, then placing the order.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
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 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 processor, or if you’re just interested in learning more about how checkout works in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
It’s recommended to go through the [Shipping Architecture Overview](../shipping.md) and [Payment Architecture Overview](../payment.md) first to have a better understanding of Medusa’s architecture.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](./implement-cart.mdx#create-a-cart) and [associated with a logged-in or guest customer](./implement-cart.mdx#associate-a-logged-in-customer-with-the-cart). The cart should also have at least [one product in it](https://docs.medusajs.com/api/store#carts_postcartscartlineitems).
|
||||
|
||||
You can learn how to implement the cart flow using [this documentation](./implement-cart.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Shipping Step
|
||||
|
||||
In this step, the customer generally enters their shipping info, then chooses the available shipping option based on the entered info.
|
||||
|
||||
### Add Shipping Address
|
||||
|
||||
After the customer enters their shipping address information, you must send a `POST` request to the [Update a Cart](https://docs.medusajs.com/api/store#carts_postcartscart) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
shipping_address: {
|
||||
company,
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
address_2,
|
||||
city,
|
||||
country_code,
|
||||
province,
|
||||
postal_code,
|
||||
phone,
|
||||
},
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_address)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const addShippingAddress =
|
||||
(address: Record<string, string>) => {
|
||||
updateCart.mutate({
|
||||
shipping_address: {
|
||||
company: address.company,
|
||||
first_name: address.first_name,
|
||||
last_name: address.last_name,
|
||||
address_1: address.address_1,
|
||||
address_2: address.address_2,
|
||||
city: address.city,
|
||||
country_code: address.country_code,
|
||||
province: address.province,
|
||||
postal_code: address.postal_code,
|
||||
phone: address.phone,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
shipping_address: {
|
||||
company,
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
address_2,
|
||||
city,
|
||||
country_code,
|
||||
province,
|
||||
postal_code,
|
||||
phone,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_address)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter and the new shipping address in the request body.
|
||||
|
||||
The request returns the updated cart, with the new shipping address available in `cart.shipping_address`.
|
||||
|
||||
---
|
||||
|
||||
### List Shipping Options
|
||||
|
||||
After updating the cart with the customer’s address, the list of available [shipping options](../shipping.md#shipping-option) for that cart might change. So, you should retrieve the updated list of options.
|
||||
|
||||
You can retrieve the list of shipping options by sending a `GET` request to the [Retrieve Shipping Options for Cart API](https://docs.medusajs.com/api/store#shipping-options_getshippingoptionscartid) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.shippingOptions.listCartOptions(cartId)
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCartShippingOptions } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
cartId: string
|
||||
}
|
||||
|
||||
const ShippingOptions = ({ cartId }: Props) => {
|
||||
const { shipping_options, isLoading } =
|
||||
useCartShippingOptions(cartId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{shipping_options && !shipping_options.length && (
|
||||
<span>No shipping options</span>
|
||||
)}
|
||||
{shipping_options && (
|
||||
<ul>
|
||||
{shipping_options.map(
|
||||
(shipping_option) => (
|
||||
<li key={shipping_option.id}>
|
||||
{shipping_option.name}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShippingOptions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/shipping-options/${cartId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter. It returns the array of [shipping options](https://docs.medusajs.com/api/store#shipping-options_getshippingoptions). Typically you would display those options to the customer to choose from.
|
||||
|
||||
### Choose Shipping Option
|
||||
|
||||
Once the customer chooses one of the available shipping options, send a `POST` request to the [Add a Shipping Method](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod) API endpoint. This will create a [shipping method](../shipping.md#shipping-method) based on the shipping option chosen and will associate it with the customer’s cart:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.addShippingMethod(cartId, {
|
||||
option_id: shippingOptionId, // the ID of the selected option
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_methods)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAddShippingMethodToCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const ShippingOptions = ({ cartId }: Props) => {
|
||||
// ...
|
||||
const addShippingMethod = useAddShippingMethodToCart(cartId)
|
||||
|
||||
const handleAddShippingMethod = (option_id: string) => {
|
||||
addShippingMethod.mutate({
|
||||
option_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ShippingOptions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/shipping-methods`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
option_id: shippingOptionId, // ID of the selected option
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_methods)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter and its body the ID of the selected shipping option.
|
||||
|
||||
It returns the updated cart, with the created shipping method available in the array `cart.shipping_methods`.
|
||||
|
||||
---
|
||||
|
||||
## Payment 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.
|
||||
|
||||
### Display Payment Methods
|
||||
|
||||
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](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.createPaymentSessions(cartId)
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart, startCheckout } = useCart()
|
||||
|
||||
useEffect(() => {
|
||||
startCheckout.mutate()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!cart?.payment_sessions.length && (
|
||||
<span>No payment processors</span>
|
||||
)}
|
||||
<ul>
|
||||
{cart?.payment_sessions.map(
|
||||
(paymentSession) => (
|
||||
<li key={paymentSession.id}>
|
||||
{paymentSession.provider_id}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-sessions`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint accepts the ID of the cart as a path parameter. It returns the updated cart with the initialized payment sessions available on `cart.payment_sessions`.
|
||||
|
||||
### Select Payment Session
|
||||
|
||||
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](https://docs.medusajs.comapi/store#carts_postcartscartpaymentsession) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.setPaymentSession(cartId, {
|
||||
// retrieved from the payment session selected by the customer
|
||||
provider_id: paymentProviderId,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart, startCheckout, pay } = useCart()
|
||||
// ...
|
||||
|
||||
const handleSetPaymentSession = (provider_id: string) => {
|
||||
pay.mutate({
|
||||
provider_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-session`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
// the payment session selected by the customer
|
||||
provider_id: paymentProviderId,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
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 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 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](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessionupdate) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
|
||||
data: {
|
||||
// pass any data you want to add in the `data` attribute
|
||||
// for example:
|
||||
"test": true,
|
||||
},
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session.data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdatePaymentSession, useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart } = useCart()
|
||||
const updatePaymentSession = useUpdatePaymentSession(cart.id)
|
||||
// ...
|
||||
|
||||
const handleUpdatePaymentSession = (
|
||||
provider_id: string,
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
updatePaymentSession.mutate({
|
||||
provider_id,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/store/carts/${cartId}/payment-sessions/${paymentProviderId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
// pass any data you want to add in the `data` attribute
|
||||
// for example:
|
||||
"test": true,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session.data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
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 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](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.complete(cartId)
|
||||
.then(({ type, data }) => {
|
||||
console.log(type, data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const Cart = () => {
|
||||
const { completeCheckout } = useCart()
|
||||
// ...
|
||||
|
||||
const handleCompleteCheckout = () => {
|
||||
completeCheckout.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/complete`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ type, data }) => {
|
||||
console.log(type, data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter.
|
||||
|
||||
The request returns two properties: `type` and `data`. If the order was placed successfully, `type` will be `order` and `data` will be the order's data.
|
||||
|
||||
If an error occurred while placing the order, `type` will be `cart` and `data` will be the cart's data.
|
||||
@@ -0,0 +1,738 @@
|
||||
---
|
||||
description: 'Learn how to implement customer group functionalities for admins using the REST APIs. This includes listing customer groups, creating a customer group, managing customers in the group, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Customer Groups
|
||||
|
||||
In this document, you’ll learn how to use the customer groups admin APIs to manage customer groups and their associated customers and price lists.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the Admin API you can manage customer groups by creating, retrieving, updating, and deleting them. You can also manage the customers in a customer group.
|
||||
|
||||
Using the PriceList API you can specify among the conditions the customer groups that the prices will apply to.
|
||||
|
||||
This guide covers how to use these APIs to perform these tasks.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Create Customer Groups
|
||||
|
||||
You can create a customer group by sending a request to the Create Customer Group endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.create({
|
||||
name: "VIP",
|
||||
})
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateCustomerGroup } from "medusa-react"
|
||||
|
||||
const CreateCustomerGroup = () => {
|
||||
const createCustomerGroup = useAdminCreateCustomerGroup()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createCustomerGroup.mutate({
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customer-groups`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "VIP",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/customer-groups' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "VIP"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the `name` parameter and optionally accepts the `metadata` object parameter to be passed in the body. It returns the created customer group.
|
||||
|
||||
---
|
||||
|
||||
## List Customer Groups
|
||||
|
||||
You can get a list of all customer groups by sending a request to the List Customer Groups endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.list()
|
||||
.then(({ customer_groups, limit, offset, count }) => {
|
||||
console.log(customer_groups.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
|
||||
const CustomerGroups = () => {
|
||||
const {
|
||||
customer_groups,
|
||||
isLoading,
|
||||
} = useAdminCustomerGroups()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{customer_groups && !customer_groups.length && (
|
||||
<span>No Customer Groups</span>
|
||||
)}
|
||||
{customer_groups && customer_groups.length > 0 && (
|
||||
<ul>
|
||||
{customer_groups.map(
|
||||
(customerGroup: CustomerGroup) => (
|
||||
<li key={customerGroup.id}>
|
||||
{customerGroup.name}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerGroups
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customer-groups`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_groups, limit, offset, count }) => {
|
||||
console.log(customer_groups.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/customer-groups' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of customer groups, as well as pagination fields.
|
||||
|
||||
You can also pass filters and other selection query parameters to the request. Check out the [API reference](https://docs.medusajs.com/api/admin#customer-groups_getcustomergroups) for more details on available query parameters.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Customer Group
|
||||
|
||||
You can retrieve a single customer group by sending a request to the Get a Customer Group endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.retrieve(customerGroupId)
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCustomerGroup } from "medusa-react"
|
||||
|
||||
const CustomerGroup = () => {
|
||||
const { customer_group, isLoading } = useAdminCustomerGroup(
|
||||
customerGroupId
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{customer_group && <span>{customer_group.name}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/customer-groups/${customerGroupId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/customer-groups/<CUSTOMER_GROUP_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the customer group to retrieve as a path parameter. It returns the customer group of that ID.
|
||||
|
||||
---
|
||||
|
||||
## Update a Customer Group
|
||||
|
||||
You can update a customer group’s data by sending a request to the Update Customer Group endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.update(customerGroupId, {
|
||||
metadata: {
|
||||
is_seller: true,
|
||||
},
|
||||
})
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateCustomerGroup } from "medusa-react"
|
||||
|
||||
const UpdateCustomerGroup = () => {
|
||||
const updateCustomerGroup = useAdminUpdateCustomerGroup(
|
||||
customerGroupId
|
||||
)
|
||||
// ..
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateCustomerGroup.mutate({
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateCustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/customer-groups/${customerGroupId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
metadata: {
|
||||
is_seller: true,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/customer-groups/<CUSTOMER_GROUP_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"metadata": {
|
||||
"is_seller": true
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the customer group as a path parameter, and optionally accepts the `name` or `metadata` fields as body parameters. It returns the updated customer group.
|
||||
|
||||
---
|
||||
|
||||
## Delete Customer Group
|
||||
|
||||
You can delete a customer group by sending a request to the Delete a Customer Group endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.delete(customerGroupId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteCustomerGroup } from "medusa-react"
|
||||
|
||||
const CustomerGroup = () => {
|
||||
const deleteCustomerGroup = useAdminDeleteCustomerGroup(
|
||||
customerGroupId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDeleteCustomerGroup = () => {
|
||||
deleteCustomerGroup.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/customer-groups/${customerGroupId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.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/customer-groups/<CUSTOMER_GROUP_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the customer group to delete as a path parameter. It returns the ID of the deleted entity.
|
||||
|
||||
---
|
||||
|
||||
## Manage Customers
|
||||
|
||||
### Add Customer to Group
|
||||
|
||||
You can add a customer to a group by sending a request to the Customer Group’s Add Customer endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.addCustomers(customerGroupId, {
|
||||
customer_ids: [
|
||||
{
|
||||
id: customerId,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminAddCustomersToCustomerGroup,
|
||||
} from "medusa-react"
|
||||
|
||||
const CustomerGroup = () => {
|
||||
const addCustomers = useAdminAddCustomersToCustomerGroup(
|
||||
customerGroupId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAddCustomers= (customerId: string) => {
|
||||
addCustomers.mutate({
|
||||
customer_ids: [
|
||||
{
|
||||
id: customerId,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/customer-groups/${customerGroupId}/customers/batch`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_ids: [
|
||||
{
|
||||
id: customerId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/customer-groups/<CUSTOMER_GROUP_ID>/customers/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"customer_ids": [
|
||||
{
|
||||
"id": "<CUSTOMER_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the customer group as a path parameter. In its body, it accepts a `customer_ids` array of objects. Each object in the array must have the `id` property with its value being the ID of the customer you want to add.
|
||||
|
||||
### List Customers
|
||||
|
||||
You can retrieve a list of all customers in a customer group using the List Customers endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.listCustomers(customerGroupId)
|
||||
.then(({ customers, count, offset, limit }) => {
|
||||
console.log(customers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { useAdminCustomerGroupCustomers } from "medusa-react"
|
||||
|
||||
const CustomerGroup = () => {
|
||||
const {
|
||||
customers,
|
||||
isLoading,
|
||||
} = useAdminCustomerGroupCustomers(
|
||||
customerGroupId
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{customers && !customers.length && (
|
||||
<span>No customers</span>
|
||||
)}
|
||||
{customers && customers.length > 0 && (
|
||||
<ul>
|
||||
{customers.map((customer: Customer) => (
|
||||
<li key={customer.id}>{customer.first_name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customer-groups/${customerGroupId}/customers`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customers, count, offset, limit }) => {
|
||||
console.log(customers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/customer-groups/<CUSTOMER_GROUP_ID>/customers' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the customer group as a path parameter. It returns an array of customers along with pagination fields.
|
||||
|
||||
### Remove Customers from a Group
|
||||
|
||||
:::info
|
||||
|
||||
Removing customers from a group does not remove them entirely. They’ll still be available in your store.
|
||||
|
||||
:::
|
||||
|
||||
You can remove customers from a customer group by sending a request to the Remove Customers endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customerGroups.removeCustomers(
|
||||
customer_group_id,
|
||||
{
|
||||
customer_ids: [
|
||||
{
|
||||
id: customer_id,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import {
|
||||
useAdminRemoveCustomersFromCustomerGroup,
|
||||
} from "medusa-react"
|
||||
|
||||
const CustomerGroup = () => {
|
||||
const removeCustomers =
|
||||
useAdminRemoveCustomersFromCustomerGroup(
|
||||
customerGroupId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleRemoveCustomer = (customer_id: string) => {
|
||||
removeCustomers.mutate({
|
||||
customer_ids: [
|
||||
{
|
||||
id: customer_id,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CustomerGroup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/customer-groups/${customerGroupId}/customers/batch`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_ids: [
|
||||
{
|
||||
id: customerId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer_group }) => {
|
||||
console.log(customer_group.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/customer-groups/<CUSTOMER_GROUP_ID>/customers/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"customer_ids": [
|
||||
{
|
||||
"id": "<CUSTOMER_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as a path parameter the ID of the customer group to remove customers from. In its body, it accepts a `customer_ids` array of objects. Each object in the array must have the `id` property with the value being the ID of the customer to remove from the group.
|
||||
|
||||
This request returns the customer group.
|
||||
|
||||
---
|
||||
|
||||
## Use Customer Groups as Conditions in a Price List
|
||||
|
||||
When you create or update a price list, you can specify one or more customer groups as conditions for the price list. You can learn how to do that in the [PriceList API documentation](../../price-lists/admin/manage-price-lists.mdx).
|
||||
@@ -0,0 +1,338 @@
|
||||
---
|
||||
description: 'Learn how to implement customer-related functionalities for admins using the REST APIs. This includes how to list customers, add a new customer and edit the details of a customer.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Customers
|
||||
|
||||
In this document, you’ll learn how to implement customer management functionalities for admin users.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the customer admin REST APIs, you can manage customers, including creating, updating, and listing them.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List customers
|
||||
- Add a new customer
|
||||
- Edit a customer’s details
|
||||
|
||||
---
|
||||
|
||||
## 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, JavaScript’s Fetch API, or cURL.
|
||||
|
||||
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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Customers
|
||||
|
||||
You can show a list of customers by sending a request to the [List Customers](https://docs.medusajs.com/api/admin#customers_getcustomers) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customers.list()
|
||||
.then(({ customers, limit, offset, count }) => {
|
||||
console.log(customers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { useAdminCustomers } from "medusa-react"
|
||||
|
||||
const Customers = () => {
|
||||
const { customers, isLoading } = useAdminCustomers()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{customers && !customers.length && (
|
||||
<span>No customers</span>
|
||||
)}
|
||||
{customers && customers.length > 0 && (
|
||||
<ul>
|
||||
{customers.map((customer: Customer) => (
|
||||
<li key={customer.id}>{customer.first_name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Customers
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customers`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customers, limit, offset, count }) => {
|
||||
console.log(customers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/customers' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request doesn’t require any path or query parameters. You can pass it optional parameters used for filtering and pagination. Check out the [API reference](https://docs.medusajs.com/api/admin#customers_getcustomers) to learn more.
|
||||
|
||||
This request returns the following data in the response:
|
||||
|
||||
- `customers`: An array of customers.
|
||||
- `limit`: The maximum number of customers that can be returned in the request.
|
||||
- `offset`: The number of customers skipped in the result.
|
||||
- `count`: The total number of customers available.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about pagination in the [API reference](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Create a Customer
|
||||
|
||||
Admins can create customer accounts. They have to supply the customer’s credentials and basic info.
|
||||
|
||||
You can create a customer account by sending a request to the [Create a Customer](https://docs.medusajs.com/api/admin#customers_postcustomers) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customers.create({
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateCustomer } from "medusa-react"
|
||||
|
||||
const CreateCustomer = () => {
|
||||
const createCustomer = useAdminCreateCustomer()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
// ...
|
||||
createCustomer.mutate({
|
||||
first_name,
|
||||
last_name,
|
||||
email,
|
||||
password,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCustomer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customers`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/customers' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "<EMAIL>",
|
||||
"first_name": "<FIRST_NAME>",
|
||||
"last_name": "<LAST_NAME>",
|
||||
"password": "<PASSWORD>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the following body parameters:
|
||||
|
||||
- `email`: The email of the customer.
|
||||
- `password`: The password of the customer.
|
||||
- `first_name`: The customer’s first name.
|
||||
- `last_name`: the customer’s last name.
|
||||
|
||||
You can also pass other optional parameters. To learn more, check out the [API reference](https://docs.medusajs.com/api/admin#customers_postcustomers).
|
||||
|
||||
The request returns the created customer object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Edit Customer’s Information
|
||||
|
||||
An admin can edit a customer’s basic information and credentials.
|
||||
|
||||
You can edit a customer’s information by sending a request to the [Update a Customer](https://docs.medusajs.com/api/admin#customers_postcustomerscustomer) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.customers.update(customerId, {
|
||||
first_name,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateCustomer } from "medusa-react"
|
||||
|
||||
const UpdateCustomer = () => {
|
||||
const updateCustomer = useAdminUpdateCustomer(customerId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
// ...
|
||||
updateCustomer.mutate({
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateCustomer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/customers/${customerId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/customers/<CUSTOMER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"first_name": "<FIRST_NAME>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts any of the customer’s fields as body parameters. In this example, you update the customer’s first name. You can learn more about accepted body parameters in the [API reference](https://docs.medusajs.com/api/admin#customers_postcustomerscustomer).
|
||||
|
||||
This request returns the updated customer object in the response.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement customer profiles in the storefront](../storefront/implement-customer-profiles.mdx)
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
description: 'Learn how to send a confirmation email to the customer after they sign up. This guide uses SendGrid as an example Notification provider.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Send Sign Up Confirmation Email
|
||||
|
||||
In this document, you’ll learn how to send a confirmation email to the customer after they sign up.
|
||||
|
||||
## Overview
|
||||
|
||||
When a customer registers, the event `customer.created` is triggered. You can then listen to this event in a subscriber to perform an action, such as send the customer a confirmation email.
|
||||
|
||||
This guide will explain how to create the subscriber and how to use SendGrid to send the confirmation email. SendGrid is only used to illustrate how the process works, but you’re free to use any other notification service.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Event Bus Module
|
||||
|
||||
The event bus module trigger the event to the listening subscribers. So, it’s required to have an event bus module installed and configured on your Medusa backend.
|
||||
|
||||
The [Local Event Bus module](../../../development/events/modules/local.md) works in a development environment. For production, it’s recommended to use the [Redis Event Bus module](../../../development/events/modules/redis.md).
|
||||
|
||||
### Notification Provider
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Subscriber
|
||||
|
||||
To subscribe to and handle an event, you must create a subscriber.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
|
||||
|
||||
:::
|
||||
|
||||
Create the file `src/subscribers/customer-confirmation.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/customer-confirmation.ts
|
||||
type InjectedDependencies = {
|
||||
// TODO add necessary dependencies
|
||||
}
|
||||
|
||||
class CustomerConfirmationSubscriber {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// TODO subscribe to event
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomerConfirmationSubscriber
|
||||
```
|
||||
|
||||
You’ll be adding in the next step the necessary dependencies to the subscriber.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Subscribe to the Event
|
||||
|
||||
In this step, you’ll subscribe to the `customer.created` event to send the customer a confirmation email.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
### Method 1: Using the NotificationService
|
||||
|
||||
If the notification provider you’re using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
|
||||
|
||||
```ts title=src/subscribers/customer-confirmation.ts
|
||||
import { NotificationService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
notificationService: NotificationService
|
||||
}
|
||||
|
||||
class CustomerConfirmationSubscriber {
|
||||
constructor({ notificationService }: InjectedDependencies) {
|
||||
notificationService.subscribe(
|
||||
"customer.created",
|
||||
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomerConfirmationSubscriber
|
||||
```
|
||||
|
||||
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
:::
|
||||
|
||||
### Method 2: Using the EventBusService
|
||||
|
||||
If the notification provider you’re using isn’t configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
|
||||
|
||||
```ts title=src/subscribers/customer-confirmation.ts
|
||||
import { Customer, EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
class CustomerConfirmationSubscriber {
|
||||
constructor({ eventBusService }: InjectedDependencies) {
|
||||
eventBusService.subscribe(
|
||||
"customer.created",
|
||||
this.handleCustomerConfirmation
|
||||
)
|
||||
}
|
||||
|
||||
handleCustomerConfirmation = async (data: Customer) => {
|
||||
// TODO: handle event
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomerConfirmationSubscriber
|
||||
```
|
||||
|
||||
When using this method, you’ll have to handle the logic of sending the confirmation email to the customer inside the handler function, which in this case is `handleCustomerConfirmation`.
|
||||
|
||||
## Step 3: Handle the Event
|
||||
|
||||
The `handleCustomerConfirmation` method receives a `data` object as a parameter which is a payload emitted when the event was triggered. This object is the entire customer object. So, you can find in it fields like `first_name`, `last_name`, `email`, and more.
|
||||
|
||||
In this method, you should typically send an email to the customer. You can place any content in the email, such as welcoming them to your store or thanking them for registering.
|
||||
|
||||
### Example: Using SendGrid
|
||||
|
||||
For example, you can implement this subscriber to send emails using SendGrid:
|
||||
|
||||
```ts title=src/subscribers/customer-confirmation.ts
|
||||
import { Customer, EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService,
|
||||
sendgridService: any
|
||||
}
|
||||
|
||||
class CustomerConfirmationSubscriber {
|
||||
protected sendGridService: any
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
}: InjectedDependencies) {
|
||||
this.sendGridService = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"customer.created",
|
||||
this.handleCustomerConfirmation
|
||||
)
|
||||
}
|
||||
|
||||
handleCustomerConfirmation = async (data: Customer) => {
|
||||
this.sendGridService.sendEmail({
|
||||
templateId: "customer-confirmation",
|
||||
from: "hello@medusajs.com",
|
||||
to: data.email,
|
||||
data: {
|
||||
// any data necessary for your template...
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomerConfirmationSubscriber
|
||||
```
|
||||
|
||||
Notice that you should replace the values in the object passed to the `sendEmail` method:
|
||||
|
||||
- `templateId`: Should be the ID of your confirmation email template in SendGrid.
|
||||
- `from`: Should be the from email.
|
||||
- `to`: Should be the customer’s email.
|
||||
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
description: 'Learn what Customer Groups are and how they can be used in the Medusa backend. Customer Groups allow to combine customers with similar attributes into a single group.'
|
||||
---
|
||||
|
||||
# Customer Groups
|
||||
|
||||
In this document, you’ll learn about Customer Groups and how they can be used in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Customer groups allow you to combine customers with similar attributes such as purchase habits, region, or for any reason that works for your business model.
|
||||
|
||||
You can then assign different prices for customer groups using price lists.
|
||||
|
||||
### Examples Use Cases
|
||||
|
||||
The customer groups feature can be used in a variety of use cases including:
|
||||
|
||||
- Implement a B2B business model by assigning a specific customer group for wholesale customers.
|
||||
- Combine customers that make purchases frequently into a group and give them a special discount.
|
||||
- Create different customer groups based on different buyer personas.
|
||||
|
||||
---
|
||||
|
||||
## CustomerGroup Entity Overview
|
||||
|
||||
A customer group is stored in the database as a [CustomerGroup](../../references/entities/classes/CustomerGroup.md) entity. This entity has two attributes other than the `id`: `name` and `metadata`.
|
||||
|
||||
Similar to all entities in Medusa, you can use the `metadata` object attribute to store any custom data you want. For example, you can add some flag or tag to the customer group for a custom use case:
|
||||
|
||||
```js noReport
|
||||
metadata: {
|
||||
is_seller: true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
### Customer
|
||||
|
||||
A customer can belong to multiple customer groups, and a customer group can have more than one customer. After creating a customer group, you can manage customers in that group.
|
||||
|
||||
The relation between the `Customer` and `CustomerGroup` entities is available on both entities:
|
||||
|
||||
- You can access the customer groups of a customer by expanding the `groups` relation and accessing `customer.groups`.
|
||||
- You can access the customers in a customer group by expanding the `customers` relation and accessing `customerGroup.customers`.
|
||||
|
||||
### PriceList
|
||||
|
||||
A price list can have multiple conditions to define in which cases it should be applied. One of those conditions is customer groups. You can specify more than one customer group to apply the prices on.
|
||||
|
||||
The relation between the `PriceList` and `CustomerGroup` entities is available on both entities:
|
||||
|
||||
- You can access the customer groups of a price list by expanding the `customer_groups` relation and accessing `price_list.customer_groups`.
|
||||
- You can access the price lists that are applied to a customer group by expanding the `price_lists` relation and accessing `customerGroup.price_lists`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Manage customer groups using the Admin APIs](./admin/manage-customer-groups.mdx)
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
description: 'Learn about what Customers are in Medusa and how they are implemented. Customers are individuals that make purchases in your store.'
|
||||
---
|
||||
|
||||
# Customers
|
||||
|
||||
In this document, you’ll learn about Customers and their relation to other entities in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Customers are individuals that make purchases in your store. In Medusa, there are two types of customers: registered customers and guests or unregistered customers.
|
||||
|
||||
Both registered and unregistered customers can make purchases. However, only registered customers can log into their accounts and manage their details and orders.
|
||||
|
||||
An admin user can view and manage their customers, their details, their orders, and what customer group they’re in.
|
||||
|
||||
---
|
||||
|
||||
## Customer Entity Overview
|
||||
|
||||
A customer is stored in the database as a `Customer` entity. A customer has attributes related to the customer’s details such as `first_name`, `last_name`, and `phone`. However, the only required attribute is `email`.
|
||||
|
||||
### has_account Attribute
|
||||
|
||||
As mentioned earlier, customers can be either registered or unregistered. The type of customer is identified in the `has_account` attribute. This is a boolean attribute that indicates whether the customer is registered.
|
||||
|
||||
For example, when a guest customer places an order, a new `Customer` record is created with the email used (if it doesn’t already exist) and the value for `has_account` is `false`. When the unregistered customer creates an account using the same email, a new `Customer` record will be created with the value of `has_account` set to `true`.
|
||||
|
||||
### Email Uniqueness
|
||||
|
||||
An email is unique to a type of customer. So, an email can be associated with only one registered customer (where `has_account` is `true`), and one unregistered customer (where `has_account` is `false`).
|
||||
|
||||
In the example mentioned above, after the unregistered customer places an order with an email, then creates an account with the same email, two `Customer` records are created. Each of these records have different `has_account` value.
|
||||
|
||||
:::info
|
||||
|
||||
This architecture allows creating the Claim Order flow, where a registered customer can claim an order they placed as an unregistered customer. You can learn more about it in [this documentation](../orders/storefront/implement-claim-order.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
### CustomerGroup
|
||||
|
||||
Customer groups allow dividing customers into groups of similar attributes, then apply special pricing or rules for these customer groups.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about customer groups in [this documentation](./customer-groups.md).
|
||||
|
||||
:::
|
||||
|
||||
A customer can belong to more than one customer group. The relation between the `Customer` and `CustomerGroup` entities is available on both entities:
|
||||
|
||||
- You can access the customer groups of a customer by expanding the `groups` relation and accessing `customer.groups`.
|
||||
- You can access the customers in a customer group by expanding the `customers` relation and accessing `customerGroup.customers`.
|
||||
|
||||
### Orders
|
||||
|
||||
Customers can have more than one order. The relation between the `Customer` and `Order` entities is available on both entities:
|
||||
|
||||
- You can access the orders of a customer by expanding the `orders` relation and accessing `customer.orders`.
|
||||
- You can access the customer that placed an order by expanding the `customer` relation and accessing `order.customer`.
|
||||
|
||||
### Address
|
||||
|
||||
A customer can have a billing address and more than one shipping address. Both billing and shipping addresses are represented by the `Address` entity.
|
||||
|
||||
The relation between the `Customer` and `Address` entities is available on both entities:
|
||||
|
||||
- You can access the billing address of a customer by expanding the `billing_address` relation and accessing `customer.billing_address`. You can also access the shipping addresses of a customer by expanding the `shipping_addresses` relation and accessing `customer.shipping_addresses`.
|
||||
- Likewise, you can access the customer that an address is associated with by expanding the `customer` relation and accessing `address.customer`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement customer profiles in the storefront](./storefront/implement-customer-profiles.mdx)
|
||||
- [Manage customers using the admin APIs](./admin/manage-customers.mdx)
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
description: "Customers are individuals that make purchases in your store. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Customers
|
||||
|
||||
Customers are individuals that make purchases in your store. This overview introduces the available features related to customers.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Customers user guide](../../user-guide/customers/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Customer Accounts
|
||||
|
||||
Customers can make purchases as guests, or they can create an account.
|
||||
|
||||
When a customer creates an account, they can manage their details and review their order history.
|
||||
|
||||
An admin can manage all customers and their details.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/storefront/implement-customer-profiles',
|
||||
label: 'Storefront: Add Customer Profiles',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement customer accounts in a storefront.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/admin/manage-customers',
|
||||
label: 'Admin: Manage Customers',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage customers using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/customers/manage',
|
||||
label: 'User Guide: Manage Customers',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage customers in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Customer Groups
|
||||
|
||||
Admins can segment customers or assign them to different customer groups. This can be useful for marketing purposes.
|
||||
|
||||
For example, an admin can specify a different pricing or special discounts for specific customer groups.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/admin/manage-customer-groups',
|
||||
label: 'Admin: Manage Customer Groups',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage customer groups using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/customers/groups',
|
||||
label: 'User Guide: Customer Groups',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage customer groups in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/admin#customer-groups',
|
||||
label: 'Admin APIs: Customer Groups',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Admin REST APIs for Customer Groups.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how Customer-related entities are built, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers',
|
||||
label: 'Architecture: Customer',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Customer Architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/customer-groups',
|
||||
label: 'Architecture: Customer Group',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Customer Group Architecture.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Customers’ relation to other modules in Medusa.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/overview',
|
||||
label: 'Orders',
|
||||
customProps: {
|
||||
icon: Icons['check-circle-solid'],
|
||||
description: 'Customers can place orders, request returns and exchanges, and more.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/overview',
|
||||
label: 'Discounts',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Discounts can be associated with a specific customer group.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/overview',
|
||||
label: 'Price Lists',
|
||||
customProps: {
|
||||
icon: Icons['currency-dollar-solid'],
|
||||
description: 'Set special prices for specific customer groups.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,668 @@
|
||||
---
|
||||
description: 'Learn how to implement customer account functionalities in your storefront using the REST APIs. This includes implementing customer registration, login, edit profile, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Implement Customer Profiles
|
||||
|
||||
In this document, you’ll learn how to implement customer account functionalities in a storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa provides the necessary functionalities and endpoints to allow integrating essential customer features. Customers can create accounts to manage their information and keep track of their orders.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to implement the following features in a storefront:
|
||||
|
||||
- Customer registration
|
||||
- Customer login and logout
|
||||
- Allow customers to reset their password
|
||||
- Allow customers to manage their basic information and shipping addresses
|
||||
- Show customers their orders
|
||||
|
||||
:::note
|
||||
|
||||
You can use Medusa’s Store APIs to achieve more functionalities as well. Check out the [API reference](https://docs.medusajs.com/api/store#customers) to learn more.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
---
|
||||
|
||||
## Register a Customer
|
||||
|
||||
A customer can register with an email and a password to store and manage their data.
|
||||
|
||||
You can register a new customer by sending a request to the [Create a Customer](https://docs.medusajs.com/api/store#customers_postcustomers) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.create({
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateCustomer } from "medusa-react"
|
||||
|
||||
const RegisterCustomer = () => {
|
||||
const createCustomer = useCreateCustomer()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
// ...
|
||||
createCustomer.mutate({
|
||||
first_name,
|
||||
last_name,
|
||||
email,
|
||||
password,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterCustomer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the following body parameters:
|
||||
|
||||
- `email`: An email used to log in after registration. This email must be unique. You can check if an email is unique using the “[Check if email exists](https://docs.medusajs.com/api/store#auth_getauthemail)” endpoint.
|
||||
- `password`: A password used to log in after registration.
|
||||
- `first_name`: The customer’s first name.
|
||||
- `last_name`: The customer’s last name.
|
||||
|
||||
This request also accepts optional body parameters, which you can check out in the [API reference](https://docs.medusajs.com/api/store#customers_postcustomers).
|
||||
|
||||
It returns the created customer object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Log in a Customer
|
||||
|
||||
A customer can log in to your store to manage their data and make purchases using their account.
|
||||
|
||||
You can log in a customer into your store by sending a request to the [Customer Login](https://docs.medusajs.com/api/store#auth_postauth) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.auth.authenticate({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/auth`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the body parameters `email` and `password`. It returns the customer object in the response.
|
||||
|
||||
If you’re using the Medusa JS Client, the customer’s session will already be set and used in all future requests.
|
||||
|
||||
However, if you’re using the Fetch API, you must include the option `credentials` with the value `include` to make sure all future requests are authenticated.
|
||||
|
||||
---
|
||||
|
||||
## Log out a Customer
|
||||
|
||||
You can log out a customer by sending a request to the [Customer Logout](https://docs.medusajs.com/api/store#auth_deleteauth) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.auth.deleteSession()
|
||||
.then(() => {
|
||||
// success
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/auth`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
// success
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If this request is successful, the customer’s session will be deleted and they’ll be logged out.
|
||||
|
||||
---
|
||||
|
||||
## Reset Password
|
||||
|
||||
Customers might need to reset their password in case they forget it. To reset a customer’s password, you need to implement two steps.
|
||||
|
||||
### Step 1: Request Password Reset
|
||||
|
||||
The customer must first enter their account’s email. Then, if an account with that email address exists, an email will be sent to that email address with a link that points the customer to step 2.
|
||||
|
||||
You can request to reset a customer’s password by sending a request to the [Request Password Reset](https://docs.medusajs.com/api/store#customers_postcustomerscustomerpasswordtoken) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.generatePasswordToken({
|
||||
email,
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// failed
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/password-token`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// failed
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the body parameter `email`. Its value must be the email associated with the customer’s account.
|
||||
|
||||
If the request has been processed successfully, it returns a `204` status code in the response. In case it fails, an error will be thrown.
|
||||
|
||||
:::note
|
||||
|
||||
If the customer doesn’t receive an email after this request, make sure that you’ve set up a Notification provider like [SendGrid](../../../plugins/notifications/sendgrid.mdx) successfully. You also need to add a subscriber that handles the [customer.password_reset](../../../development/events/events-list.md#customer-events) event and sends the email.
|
||||
|
||||
:::
|
||||
|
||||
### Step 2: Verify and Reset Password
|
||||
|
||||
After the first step, the customer should receive an email with a link to a page in the storefront. This page should accept a `token` query parameter. Then, the customer should be prompted to enter their email and password.
|
||||
|
||||
You can then reset the customer’s password to the new password they enter by sending a request to the [Reset Password](https://docs.medusajs.com/api/store#customers_postcustomersresetpassword) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.resetPassword({
|
||||
email,
|
||||
password,
|
||||
token,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/password-reset`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
token,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the following body parameters:
|
||||
|
||||
- `email`: The email of the customer. This must be the email associated with the account.
|
||||
- `password`: The new password the customer wants to use for their account.
|
||||
- `token`: The token passed as a query into the page.
|
||||
|
||||
If successful, this request returns the customer object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Edit a Customer’s Info
|
||||
|
||||
A logged-in customer can edit their info, such as their first name or email address.
|
||||
|
||||
You can edit a customer’s info using the [Update Customer](https://docs.medusajs.com/api/store#customers_postcustomerscustomer) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.update({
|
||||
first_name,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdateMe } from "medusa-react"
|
||||
|
||||
const UpdateCustomer = () => {
|
||||
const updateCustomer = useUpdateMe()
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
// ...
|
||||
updateCustomer.mutate({
|
||||
id: customer_id,
|
||||
first_name,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* Render form */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateCustomer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/me`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts any of the customer’s details that should be updated as body parameters. In the example above, the `first_name` of the customer is updated. You can check out the [API reference](https://docs.medusajs.com/api/api/store#customers_postcustomerscustomer) for a full list of accepted body parameters.
|
||||
|
||||
It returns in the response the updated customer object.
|
||||
|
||||
---
|
||||
|
||||
## Manage Shipping Addresses
|
||||
|
||||
A logged-in customer uses their shipping addresses during the checkout process. They can have more than one shipping address, and they can choose one of them when placing an order.
|
||||
|
||||
:::tip
|
||||
|
||||
The customer object returned in the requests mentioned in this document include a `shipping_addresses` property. It’s an array of the customer’s shipping addresses. You can access it to display the customer’s shipping addresses.
|
||||
|
||||
:::
|
||||
|
||||
### Add a Shipping Address
|
||||
|
||||
You can add a shipping address to a customer’s account by sending a request to the [Add a Shipping Address](https://docs.medusajs.com/api/store#customers_postcustomerscustomeraddresses) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.addresses.addAddress({
|
||||
address: {
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
city,
|
||||
country_code,
|
||||
postal_code,
|
||||
phone,
|
||||
company,
|
||||
address_2,
|
||||
province,
|
||||
metadata,
|
||||
},
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/me/addresses`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
address: {
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
city,
|
||||
country_code,
|
||||
postal_code,
|
||||
phone,
|
||||
company,
|
||||
address_2,
|
||||
province,
|
||||
metadata,
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires an `address` object as a body parameter. The `address` object must have the following properties:
|
||||
|
||||
- `first_name`: The first name associated with the shipping address
|
||||
- `last_name`: The last name associated with the shipping address
|
||||
- `address_1`: The first address line of the shipping address.
|
||||
- `city`: The city of the shipping address.
|
||||
- `country_code`: The 2 character ISO code of the country in lower case.
|
||||
- `postal_code`: The postal code of the shipping address
|
||||
|
||||
It also accepts other optional body parameters, which you can learn more about in the [API reference](https://docs.medusajs.com/api/store#customers_postcustomerscustomeraddresses).
|
||||
|
||||
This request returns the updated customer object in the response.
|
||||
|
||||
### Edit a Shipping Address
|
||||
|
||||
You can edit a customer’s shipping address using the [Update a Shipping Address](https://docs.medusajs.com/api/store#customers_postcustomerscustomeraddressesaddress) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.addresses.updateAddress(addressId, {
|
||||
first_name,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/me/addresses/${addressId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the address’s ID as a path parameter. It accepts as a body parameter any of the address’s properties. In the example above, the `first_name` of the shipping address is updated. You can check the [API reference](https://docs.medusajs.com/api/store#customers_postcustomerscustomeraddressesaddress) for all the available body parameters.
|
||||
|
||||
This request returns the updated customer object in the response.
|
||||
|
||||
### Delete a Shipping Address
|
||||
|
||||
You can delete a shipping address by sending a request to the [Delete an Address](https://docs.medusajs.com/api/store#customers_deletecustomerscustomeraddressesaddress) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.addresses.deleteAddress(addressId)
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/me/addresses/${addressId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ customer }) => {
|
||||
console.log(customer.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the address’s ID as a path parameter. It returns in the response the updated customer object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Customer’s Orders
|
||||
|
||||
Logged-in customers can see their orders along with the orders’ details.
|
||||
|
||||
You can retrieve a customer’s orders by sending a request to the [List Orders](https://docs.medusajs.com/api/store#customers_getcustomerscustomerorders) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.customers.listOrders()
|
||||
.then(({ orders, limit, offset, count }) => {
|
||||
console.log(orders)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCustomerOrders } from "medusa-react"
|
||||
import { Order } from "@medusajs/medusa"
|
||||
|
||||
const Orders = () => {
|
||||
// refetch a function that can be used to
|
||||
// re-retrieve orders after the customer logs in
|
||||
const { orders, isLoading, refetch } = useCustomerOrders()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading orders...</span>}
|
||||
{orders?.length && (
|
||||
<ul>
|
||||
{orders.map((order: Order) => (
|
||||
<li key={order.id}>{order.display_id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Orders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/customers/me/orders`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ orders, limit, offset, count }) => {
|
||||
console.log(orders)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request doesn’t require any path or query parameters. You can, however, send optional query parameters used for filters, pagination, and sorting. You can learn more in the [API reference](https://docs.medusajs.com/api/store#customers_getcustomerscustomerorders).
|
||||
|
||||
It returns the following data in the response:
|
||||
|
||||
- `orders`: An array of orders.
|
||||
- `limit`: The maximum number of orders that can be returned in the request.
|
||||
- `offset`: The number of orders skipped in the result.
|
||||
- `count`: The total number of orders available.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about pagination in the [API reference](https://docs.medusajs.com/api/store#pagination).
|
||||
|
||||
:::
|
||||
@@ -0,0 +1,755 @@
|
||||
---
|
||||
description: 'Learn how to implement discount functionalities for admins in Medusa using the REST APIs. This includes creating, updating, and deleting discounts, and managing conditions of discounts.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Discounts using Admin APIs
|
||||
|
||||
In this document, you’ll learn how to use the Admin’s Discount APIs to manage discounts, discount conditions, and more.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to learn about the Discount architecture in-depth, check out the [Discount Architecture](../discounts.md) documentation instead.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Using Medusa’s Discount Admin APIs, you can manage discounts, their conditions, status, rules, and more. You can also manage [dynamic discounts and their discount codes](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscountdynamiccodes).
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use general admin functionalities related to:
|
||||
|
||||
- Creating discounts of different types, configurations, and rules.
|
||||
- Updating and deleting discounts.
|
||||
- Managing conditions in that discount, including adding, retrieving, updating, and removing conditions.
|
||||
|
||||
:::tip
|
||||
|
||||
You can use Medusa’s Admin APIs to achieve more functionalities as well. Check out the [API reference](https://docs.medusajs.com/api/admin#discounts) to learn more.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
# 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, JavaScript’s Fetch API, or cURL.
|
||||
|
||||
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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Create a Discount
|
||||
|
||||
You can create a discount by sending a request to the [Create Discount](https://docs.medusajs.com/api/admin#discounts_postdiscounts) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
import {
|
||||
AllocationType,
|
||||
DiscountRuleType,
|
||||
} from "@medusajs/medusa"
|
||||
// ...
|
||||
medusa.admin.discounts.create({
|
||||
code,
|
||||
rule: {
|
||||
type: DiscountRuleType.FIXED,
|
||||
value: 10,
|
||||
allocation: AllocationType.ITEM,
|
||||
},
|
||||
regions: [
|
||||
regionId,
|
||||
],
|
||||
is_dynamic: false,
|
||||
is_disabled: false,
|
||||
})
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminCreateDiscount,
|
||||
} from "medusa-react"
|
||||
import {
|
||||
AllocationType,
|
||||
DiscountRuleType,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
const CreateDiscount = () => {
|
||||
const createDiscount = useAdminCreateDiscount()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
// ...
|
||||
createDiscount.mutate({
|
||||
code,
|
||||
rule: {
|
||||
type: DiscountRuleType.FIXED,
|
||||
value: 10,
|
||||
allocation: AllocationType.ITEM,
|
||||
},
|
||||
regions: [
|
||||
regionId,
|
||||
],
|
||||
is_dynamic: false,
|
||||
is_disabled: false,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateDiscount
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/discounts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
rule: {
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "item",
|
||||
},
|
||||
regions: [
|
||||
regionId,
|
||||
],
|
||||
is_dynamic: false,
|
||||
is_disabled: false,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/discounts' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"code": "<CODE>",
|
||||
"rule": {
|
||||
"type": "fixed",
|
||||
"value": 10,
|
||||
"allocation": "item"
|
||||
},
|
||||
"regions": [
|
||||
"<REGION_ID>"
|
||||
],
|
||||
"is_dynamic": false,
|
||||
"is_disabled": false
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts [many request-body parameters](https://docs.medusajs.com/api/admin#discounts_postdiscounts) including:
|
||||
|
||||
- `code`: This parameter is required. It is a unique code. The customer redeems the discount using this code.
|
||||
- `rule`: This parameter is required. It is an object having at least the following fields:
|
||||
- `type`: A string indicating the type of discount. It can be `fixed`, `percentage`, or `free_shipping`. When using the Medusa JS Client, you must use the enum type [DiscountRuleType](../../../references/js-client/enums/internal.discountruletype/) for the value.
|
||||
- `value`: A number indicating the value of the discount. If the discount type is `fixed`, then it will be the fixed amount to discount from the cart’s totals or its items. If the discount type is `percentage`, then it will be the percentage to discount from the items in the cart. If the type is `free_shipping`, it has no effect and can be set to `0`.
|
||||
- `allocation`: A string indicating how the discount should be applied. Can be `item` or `total`. If the type is not `fixed`, then this has no effect. When using the Medusa JS Client, you must use the enum type [AllocationType](../../../references/js-client/enums/internal.allocationtype/) for the value.
|
||||
- `regions`: An array of region IDs this discount can be used in. If the type of discount is `fixed`, only one region can be passed.
|
||||
|
||||
This request returns the full `discount` object.
|
||||
|
||||
---
|
||||
|
||||
## Update Discount
|
||||
|
||||
You can update any of the discount’s information, configurations, and conditions by sending a request to the [Update Discount](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscount) endpoint.
|
||||
|
||||
For example, you can update the discount’s description and status by sending the following request:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.discounts.update(discountId, {
|
||||
is_disabled: true,
|
||||
})
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateDiscount } from "medusa-react"
|
||||
|
||||
const UpdateDiscount = () => {
|
||||
const updateDiscount = useAdminUpdateDiscount(discount_id)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
// ...
|
||||
updateDiscount.mutate({
|
||||
is_disabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateDiscount
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/discounts/${discountId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
is_disabled: true,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/discounts/<DISCOUNT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"is_disabled": true
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the discount ID as a path parameter. You can pass the parameters you want to update in the request body. In the example above, is_disabled` parameter to update it.
|
||||
|
||||
You can check the [API reference](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscount) for all the accepted parameters to update the discount.
|
||||
|
||||
This updates the discount’s information and returns the full updated `discount` object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Manage Conditions
|
||||
|
||||
### Create a Condition
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about conditions and conditions types in the [Discount Architecture](../discounts.md) documentation.
|
||||
|
||||
:::
|
||||
|
||||
You can send a request to the [Create Condition](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscountconditions) endpoint to create a condition in a discount:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
import { DiscountConditionOperator } from "@medusajs/medusa"
|
||||
// ...
|
||||
medusa.admin.discounts.createCondition(discount_id, {
|
||||
operator: DiscountConditionOperator.IN,
|
||||
products: [
|
||||
productId,
|
||||
],
|
||||
})
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDiscountCreateCondition } from "medusa-react"
|
||||
import { DiscountConditionOperator } from "@medusajs/medusa"
|
||||
|
||||
const Discount = () => {
|
||||
const createCondition = useAdminDiscountCreateCondition(
|
||||
discount_id
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCreateCondition = (
|
||||
operator: DiscountConditionOperator,
|
||||
productId: string
|
||||
) => {
|
||||
// ...
|
||||
createCondition.mutate({
|
||||
operator,
|
||||
products: [
|
||||
productId,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Discount
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/discounts/${discountId}/conditions`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
operator: "in",
|
||||
products: [
|
||||
productId,
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/discounts/<DISCOUNT_ID>/conditions' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"operator": "in",
|
||||
"products": [
|
||||
"<PRODUCT_ID>"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the discount ID as a path parameter.
|
||||
|
||||
It also requires the request-body parameter `operator` which can have one of the following values:
|
||||
|
||||
- `in` indicates that the discount should be applied to the specified resources in this condition. When using the Medusa JS Client, `DiscountConditionOperator.IN` must be used as the value.
|
||||
- `not_in` indicates that the discount should be applied to all resources except those specified in this condition. When using the Medusa JS Client, `DiscountConditionOperator.NOT_IN` must be used as the value.
|
||||
|
||||
In addition, every condition has a condition type. Based on that condition type, a different additional parameter is required for the request.
|
||||
|
||||
For example, if the condition type is product, meaning that the condition specifies which products this discount apply/doesn’t apply for, the parameter `products` is required.
|
||||
|
||||
The additional required parameter must be an array of IDs of the resources. For the previous example, `products` would be an array of product IDs.
|
||||
|
||||
You can check the [API reference](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscountconditions) for a full list of accepted parameters based on the condition type.
|
||||
|
||||
This request returns the full `discount` object in the response which includes all conditions under `discount.rule.conditions`.
|
||||
|
||||
### Retrieve Condition
|
||||
|
||||
You can retrieve a condition and its resources by sending a request to the [Get Condition](https://docs.medusajs.com/api/admin#discounts_getdiscountsdiscountconditionscondition) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.discounts.getCondition(
|
||||
discountId,
|
||||
conditionId,
|
||||
{
|
||||
expand: "products",
|
||||
}
|
||||
)
|
||||
.then(({ discount_condition }) => {
|
||||
console.log(
|
||||
discount_condition.id,
|
||||
discount_condition.products
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminGetDiscountCondition } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const DiscountCondition = () => {
|
||||
const {
|
||||
discount_condition,
|
||||
isLoading,
|
||||
} = useAdminGetDiscountCondition(
|
||||
discount_id,
|
||||
conditionId
|
||||
)
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading</span>}
|
||||
{discount_condition && (
|
||||
<>
|
||||
<span>{discount_condition.id}</span>
|
||||
<ul>
|
||||
{discount_condition.products.map(
|
||||
(product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DiscountCondition
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/discounts/${discountId}` +
|
||||
`/conditions/${conditionId}&expand=products`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ discount_condition }) => {
|
||||
console.log(
|
||||
discount_condition.id,
|
||||
discount_condition.products
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/discounts/<DISCOUNT_ID>/conditions/<CONDITION_ID>&expand=products' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as path parameters the discount ID and the condition ID. You can optionally pass a query parameter `expand` which specifies which relations to include in the returned object.
|
||||
|
||||
By default, this request returns the discount condition object without any of its resources (In the previous example the resources are the products). To ensure the resources are included in the returned object, you must pass the name of the condition’s type as a value to the `expand` query parameter.
|
||||
|
||||
In the previous example, you pass `expand=products` as a query parameter, which returns inside the `discount_condition` object a `products` attribute. The value of `products` is an array of products that belong to this condition.
|
||||
|
||||
### Update Condition
|
||||
|
||||
You can update a condition’s resources using the [Update Condition](https://docs.medusajs.com/api/admin#discounts_postdiscountsdiscountconditionscondition) endpoint. You can’t update a condition’s operator.
|
||||
|
||||
For example, to update the products in a condition:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.discounts.updateCondition(
|
||||
discountId,
|
||||
conditionId, {
|
||||
products: [
|
||||
productId1,
|
||||
productId2,
|
||||
],
|
||||
}
|
||||
)
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDiscountUpdateCondition } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const DiscountCondition = () => {
|
||||
const updateCondition = useAdminDiscountUpdateCondition(
|
||||
discount_id,
|
||||
conditionId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdateCondition = (productIds: string[]) => {
|
||||
updateCondition.mutate({
|
||||
products: productIds,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DiscountCondition
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/discounts/${discountId}/conditions/${conditionId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
products: [
|
||||
productId1,
|
||||
productId2,
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/discounts/<DISCOUNT_ID>/conditions/<CONDITION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"products": [
|
||||
"<PRODUCT_ID_1>",
|
||||
"<PRODUCT_ID_2>"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as a path parameter the discount ID and the condition ID. In its body, it accepts the resources of the same type that were used when the condition was created.
|
||||
|
||||
For example, if a condition was created for `products`, you can’t pass in the update request `product_collections`. You must pass in the update request a `products` array as well.
|
||||
|
||||
This request returns the full `discount` object with the updated condition in the response.
|
||||
|
||||
### Delete Condition
|
||||
|
||||
You can delete a condition by sending a request to the [Delete Condition](https://docs.medusajs.com/api/admin#discounts_deletediscountsdiscountconditionscondition) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.discounts.deleteCondition(
|
||||
discountId,
|
||||
conditionId
|
||||
)
|
||||
.then(({ discount }) => {
|
||||
console.log(discount)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDiscountRemoveCondition } from "medusa-react"
|
||||
|
||||
const Discount = () => {
|
||||
const deleteCondition = useAdminDiscountRemoveCondition(
|
||||
discount_id
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdateCondition = (conditionId: string) => {
|
||||
deleteCondition.mutate(conditionId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Discount
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/discounts/${discountId}/conditions/${conditionId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ discount }) => {
|
||||
console.log(discount.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/discounts/<DISCOUNT_ID>/conditions/<CONDITION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as a path parameter the discount ID and the condition ID.
|
||||
|
||||
It returns the `discount` object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Delete Discount
|
||||
|
||||
You can delete a discount by sending a request to the [Delete Discount](https://docs.medusajs.com/api/admin#discounts_deletediscountsdiscount) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.discounts.delete(discount_id)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteDiscount } from "medusa-react"
|
||||
|
||||
const Discount = () => {
|
||||
const deleteDiscount = useAdminDeleteDiscount(discount_id)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteDiscount.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Discount
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/discounts/${discountId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.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/discounts/<DISCOUNT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the discount ID as a path parameter.
|
||||
|
||||
It returns in the response the following fields:
|
||||
|
||||
- `id`: The ID of the deleted discount.
|
||||
- `object`: A string indicating the type of object deleted. By default, its value is `discount`.
|
||||
- `deleted`: A boolean value indicating whether the discount was deleted or not.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use Discounts on the storefront](../storefront/use-discounts-in-checkout.mdx)
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
description: 'Learn about the discount architecture in the Medusa backend. Discounts are used to offer promotions to the user for marketing purposes.'
|
||||
---
|
||||
|
||||
# Discounts Architecture
|
||||
|
||||
In this document, you’ll learn about Discounts architecture and how it works.
|
||||
|
||||
## What are Discounts
|
||||
|
||||
Discounts allow you to offer promotions to your users generally for marketing purposes. Customers can apply a discount code to their checkout flow to use the discount if it’s valid for them.
|
||||
|
||||
Discounts can be limited by a set of configurations and conditions. For example, you can indicate how many times a discount can be used or by which customer group. You can also specify which products this discount can be used with, among other conditions.
|
||||
|
||||
### Discount Types
|
||||
|
||||
There are three types of discounts:
|
||||
|
||||
1. Percentage discount: remove a percentage of the price every product in the cart that the discount can be applied to.
|
||||
2. Fixed discount: remove a fixed amount either of the customer’s total checkout amount or of the price every product in the cart that the discount can be applied to.
|
||||
3. Free shipping discount: remove any shipping amount from the customer’s order during checkout.
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
Discounts can be used in many use cases including:
|
||||
|
||||
1. Applying discounted amounts for wholesale or B2B customers.
|
||||
2. Creating a sale within a specific period of time (for example, a summer sale).
|
||||
3. Give your customers free shipping for a limited time.
|
||||
|
||||
---
|
||||
|
||||
## Discount Entity Overview
|
||||
|
||||
A discount is represented by the [`Discount`](../../references/entities/classes/Discount.md) entity. Some of its important attributes are:
|
||||
|
||||
- `code` is a unique code that you specify when you create the discount. Customers use this code to apply the discount during checkout. The code can only include upper-case letters and numbers.
|
||||
- `rule_id` is the ID of the rule of this discount. The `rule` attribute is the expanded object of the `DiscountRule` entity. You can use the `rule` attribute to get details regarding the discount type. You can learn more about this in the [`DiscountRule` entity overview](#discountrule-entity-overview) later.
|
||||
- `is_disabled` is a boolean value that indicates whether this discount is published or not.
|
||||
- `regions` are the regions this discount can be used in.
|
||||
- `starts_at` and `ends_at` are timestamps that indicate when the discount starts and ends. If no expiry date is set for the discount, `ends_at` will be `null`.
|
||||
- `usage_limit` is the number of times the discount can be used. If there is no limit the value will be `null`.
|
||||
- `usage_count` is the number of times the discount has been used.
|
||||
|
||||
### Dynamic Discounts
|
||||
|
||||
Dynamic discounts are a set of discount conditions and configurations that you can reuse across many discounts.
|
||||
|
||||
After creating a dynamic discount, you can use it to create “child” discounts. Child discounts have the same conditions and configurations as the “parent” dynamic discount but with a different discount code.
|
||||
|
||||
The `is_dynamic` attribute in the `Discount` entity is a boolean value that determines whether the discount is dynamic or not. Child discounts are related to the parent discount using the `parent_discount_id` attribute which is a foreign key to the same `Discount` entity.
|
||||
|
||||
---
|
||||
|
||||
## DiscountRule Entity Overview
|
||||
|
||||
Every `Discount` entity belongs to a [`DiscountRule`](../../references/entities/classes/DiscountRule.md) entity. `DiscountRule` includes details such as the type of discount, the amount to be discounted, and more.
|
||||
|
||||
Some of the `DiscountRule` entity’s important attributes are:
|
||||
|
||||
- `type` is an enum string indicating the type of the discount. It must be either `fixed`, `percentage`, or `free_shipping`.
|
||||
- `value` is the value to be discounted. The value of this depends on the value of `type`:
|
||||
- If `type` is `fixed`, `value` will be the amount to be removed either from the total or from each product this discount is applied to.
|
||||
- If `type` is `percentage`, `value` will be the percentage to be removed from each product this discount is applied to.
|
||||
- If `type` is `free_shipping`, `value` will be 0.
|
||||
- `conditions` is an array of all discount conditions, if there are any.
|
||||
|
||||
---
|
||||
|
||||
## DiscountCondition Entity Overview
|
||||
|
||||
A discount can optionally have discount conditions. Discount conditions are used to further add limitations on when the discount can be applied.
|
||||
|
||||
A [`DiscountCondition`](../../references/entities/classes/DiscountCondition.md) belongs to a `DiscountRule` entity. The `discount_rule_id` attribute indicates the ID of the `DiscountRule` it belongs to.
|
||||
|
||||
Discount conditions have an attribute `type` that indicates the condition’s type. Its value must be one of the following:
|
||||
|
||||
- `products` means the condition applies to products.
|
||||
- `product_types` means the condition applies to product types.
|
||||
- `product_collections` means the condition applies to product collections.
|
||||
- `product_tags` means the condition applies to product tags.
|
||||
- `customer_groups` means the condition applies to customer groups.
|
||||
|
||||
Each condition type can be used for one condition in a discount. For example, you can add only one `product` discount condition to a discount.
|
||||
|
||||
Discount conditions also have an attribute `operator` that indicates how the condition should be applied. It must have one of the following values:
|
||||
|
||||
- `in` means the discount can be applied only for the items specified in the condition. For example, if the `type` is `product` and the `operator` is `in`, it means this condition defines which products must be available in the customer’s cart for the discount to apply.
|
||||
- `not_in` means the discount can be applied for all items except those specified in the condition. For example, if the `type` is `product` and the `operator` is `not_in`, it means that the discount can be applied to any item in the cart that's not specified in the condition.
|
||||
|
||||
### Condition Type Relations
|
||||
|
||||
Based on the value of `type`, one of the following relations can be used to retrieve the condition’s items:
|
||||
|
||||
- `products` is an array of products that this condition applies to if the condition’s `type` is `products`. Each item of the array would be a [`DiscountConditionProduct`](../../references/entities/classes/DiscountConditionProduct.md).
|
||||
- `product_types` is an array of product types that this condition applies to if the condition’s `type` is `product_types`. Each item of the array would be a [`DiscountConditionProductType`](../../references/entities/classes/DiscountConditionProductType.md).
|
||||
- `product_collections` is an array of product collections that this condition applies to if the condition’s `type` is `product_collections`. Each item of the array would be a [`DiscountConditionProductCollection`](../../references/entities/classes/DiscountConditionProductCollection.md).
|
||||
- `product_tags` is an array of product tags that this condition applies to if the condition’s `type` is `product_tags`. Each item of the array would be a [`DiscountConditionProductTag`](../../references/entities/classes/DiscountConditionProductTag.md).
|
||||
- `customer_groups` is an array of customer groups that this condition applies to if the condition’s `type` is `customer_groups`. Each item of the array would be a [`DiscountConditionCustomerGroup`](../../references/entities/classes/DiscountConditionCustomerGroup.md).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Manage discounts using the admin APIs](./admin/manage-discounts.mdx)
|
||||
- [Use discounts on the storefront](./storefront/use-discounts-in-checkout.mdx)
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
description: "Discounts are deductions in the checkout total that are generally used for marketing and promotional purposes. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Discounts
|
||||
|
||||
Discounts are deductions in the checkout total that are generally used for marketing and promotional purposes. This overview introduces the available features related to discounts.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Discounts user guide](../../user-guide/discounts/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Discounts Management
|
||||
|
||||
Admins can create discounts with various conditions and rules that can be used to deduct a fixed or percentage amount from the cart total, or that can be used for free shipping.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/admin/manage-discounts',
|
||||
label: 'Admin: Manage Discounts',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage discounts using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/discounts/manage',
|
||||
label: 'User Guide: Manage Discounts',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage discounts using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Applying Discounts during Checkout
|
||||
|
||||
Customers can benefit from discounts by using a discount code during checkout.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/storefront/use-discounts-in-checkout',
|
||||
label: 'Storefront: Use Discounts in Checkout',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use discounts during checkout.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#carts_getcartscart',
|
||||
label: 'Store APIs: Discounts',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Discounts with carts.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how discount-related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/modules/discounts',
|
||||
label: 'Architecture: Discounts',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Discount architecture.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Discount’s relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Cart and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Customers can apply discounts during checkout.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Product',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Discounts can be associated with specific products.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,287 @@
|
||||
---
|
||||
description: 'Learn how to implement discount functionalities in your storefront using the REST APIs. This includes adding a discount to the cart, showing the discount details, and removing the discount from the cart.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Use Discounts in Checkout
|
||||
|
||||
In this document, learn how to use discounts during checkout on the storefront.
|
||||
|
||||
:::info
|
||||
|
||||
You can check out the [Discounts Architecture documentation](../discounts.md) to learn more about how discounts work.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Customers can use discounts during checkout to benefit from promotions that the merchant provides. Discounts are added to a cart using its unique code.
|
||||
|
||||
In this document, you’ll learn how to add a discount to a cart, how to display details related to the discount, and how to remove a discount from a cart.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to implement discount functionality in your store to allow customers to apply discount codes to their cart and remove them from the cart. You also want to display the discount details for the customer.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](https://docs.medusajs.com/api/store#carts_postcart) for the customer with at least [one product in it](https://docs.medusajs.com/api/store#carts_postcartscartlineitems).
|
||||
|
||||
You can learn how to implement the cart flow using [this documentation](../../../modules/carts-and-checkout/storefront/implement-cart.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Add Discount to Cart
|
||||
|
||||
The customer can enter a discount code during the checkout flow to benefit from a promotion.
|
||||
|
||||
You can add a discount to a customer’s cart by sending the [Update Cart request](https://docs.medusajs.com/api/store#carts_postcartscart):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
discounts: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.discounts)
|
||||
})
|
||||
.catch((e) => {
|
||||
// display an error to the customer
|
||||
alert("Discount is invalid")
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const addDiscount = (code: string) => {
|
||||
updateCart.mutate({
|
||||
discounts: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
discounts: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.discounts)
|
||||
})
|
||||
.catch((e) => {
|
||||
// display an error to the customer
|
||||
alert("Discount is invalid")
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the customer’s cart ID as a path parameter. In the body of the request, you can update many cart-related details, including adding discounts by passing the `discounts` field. It is an array of objects, with each object having a property `code` with its value being the discount’s unique code.
|
||||
|
||||
:::info
|
||||
|
||||
Customers can add more than one discount to their cart, however, only one non-free shipping discount is allowed per cart.
|
||||
|
||||
:::
|
||||
|
||||
If the discount is applied successfully, the entire cart object will be returned. You can access the discounts applied on the cart in the `cart.discounts` array.
|
||||
|
||||
In case the customer enters an invalid discount or the discount cannot be applied to the cart because it doesn’t meet its conditions, the request will return an error. You can handle the error in the `catch` method.
|
||||
|
||||
---
|
||||
|
||||
## Display Discount Details
|
||||
|
||||
After the customer enters a discount code and it is applied to the cart, you can display the discount details to the customer. This shows the customer how much they benefited from the discount.
|
||||
|
||||
The previous request returns the full cart object with different fields that can be used to display the discount details depending on the discount type.
|
||||
|
||||
### Display Discount Information
|
||||
|
||||
In the returned `cart` object, all discounts applied can be found in the `discounts` array. The following properties can be used to display the discount information:
|
||||
|
||||
```json
|
||||
{
|
||||
"discounts": [
|
||||
{
|
||||
"code": "TEST",
|
||||
"is_dynamic": false,
|
||||
"starts_at": "2022-12-01T15:09:21.149Z",
|
||||
"ends_at": null,
|
||||
"usage_limit": null,
|
||||
"usage_count": 0,
|
||||
"rule": {
|
||||
"description": "percentage discount",
|
||||
"type": "percentage",
|
||||
"value": 20,
|
||||
"allocation": "total",
|
||||
"conditions": [
|
||||
//...
|
||||
],
|
||||
//...
|
||||
},
|
||||
//...
|
||||
}
|
||||
],
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
- The `code` property is the code of the discount. This is the code that the customer enters to apply the discount.
|
||||
- The `starts_at` and `ends_at` indicate the start and expiry dates of the discount. If there’s no end date, the `ends_at` property will be null.
|
||||
- The `usage_limit` property indicates if there is any usage limit set on the discount. The `usage_count` property indicates how many times this discount has been used.
|
||||
- The `rule` property includes details related to the type of discount, its value, and its conditions if there are any.
|
||||
|
||||
### Display Item Discounts
|
||||
|
||||
In the returned `cart` object, the property `items` is an array of items in the cart. If a discount is applied to an item, the following properties inside the item can be used to get the item-specific discount details:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"adjustments": [
|
||||
{
|
||||
"description": "discount",
|
||||
"discount_id": "disc_01GK73RBCRXXT8HWEYMQE4RCY6",
|
||||
"amount": 730,
|
||||
//...
|
||||
}
|
||||
],
|
||||
"subtotal": 3650,
|
||||
"discount_total": 730,
|
||||
"total": 2920,
|
||||
//...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- The `adjustments` property is an array of objects, each containing details about the adjustment made to the item. Discounts applied to the item will be represented by an adjustment in this array. It will have the `description` property with the value `discount`, the `discount_id` property, and the `amount` of the discount.
|
||||
- The `subtotal` property is the price before the discounts are applied.
|
||||
- The `discount_total` property is the sum of all discount amounts.
|
||||
- The `total` property is the total of the item after applying the discounts.
|
||||
|
||||
### Display Cart Discounts
|
||||
|
||||
The returned `cart` object has the following properties that can be used to display cart’s general discount details:
|
||||
|
||||
```json
|
||||
{
|
||||
"subtotal": 11550,
|
||||
"discount_total": 1910,
|
||||
"shipping_total": 0,
|
||||
"total": 9640,
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
- The `subtotal` property is the total of the cart before applying any discount.
|
||||
- The `discount_total` property is the total discount amounts applied to the cart.
|
||||
- The `shipping_total` property is the total shipping amount. If a Free Shipping discount is applied to the cart, `shipping_total` and `discount_total` will be 0.
|
||||
- The `total` property is the total amount of the cart after applying the discount.
|
||||
|
||||
---
|
||||
|
||||
## Remove Discount from Cart
|
||||
|
||||
The customer can choose to remove a discount from their cart.
|
||||
|
||||
You can remove a discount from a customer’s cart using the [Remove Discount request](https://docs.medusajs.com/api/store#carts_deletecartscartdiscountsdiscount):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.deleteDiscount(cartId, code)
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.discounts)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/discounts/${code}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.discounts)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the cart ID and the code of the discount to remove. If the discount is removed successfully, the request returns the updated cart object, where you won’t find the discount in the `discounts` array anymore.
|
||||
|
||||
The totals of the cart and its items will be updated as well.
|
||||
@@ -0,0 +1,819 @@
|
||||
---
|
||||
description: 'Learn how to implement gift card functionalities for admins using the REST APIs. This includes how to create, update, and delete gift cards, as well as custom gift cards.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Gift Cards
|
||||
|
||||
In this document, you’ll learn how to manage gift cards using the admin APIs.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about what gift cards are and how they’re used in [this documentation](../gift-cards.md)
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Using the gift cards’ admin APIs, you can manage gift cards including listing, updating, and deleting them.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage gift card products including retrieving, adding, updating, and deleting them.
|
||||
- Managing custom gift cards including retrieving, adding, updating and deleting them.
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Manage Gift Card Product
|
||||
|
||||
This section covers managing the gift card products. Each gift card can have unlimited denominations.
|
||||
|
||||
As gift cards are, before purchase, essentially products, you’ll be using product endpoints to manage them.
|
||||
|
||||
### Retrieve Gift Card Product
|
||||
|
||||
You can retrieve the gift card products by sending a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts) endpoint, but filtering the products with the `is_giftcard` flag:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.products.list({
|
||||
is_giftcard: true,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
if (products.length) {
|
||||
// gift card products exist
|
||||
} else {
|
||||
// no gift card products are created
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import {
|
||||
PricedProduct,
|
||||
} from "@medusajs/medusa/dist/types/pricing"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
|
||||
const GiftCard = () => {
|
||||
const { products, isLoading } = useAdminProducts({
|
||||
is_giftcard: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map(
|
||||
(product: (Product | PricedProduct)) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
{products && !products.length && (
|
||||
<span>No Gift Cards</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/products?is_giftcard=true`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
if (products.length) {
|
||||
// gift card products exist
|
||||
} else {
|
||||
// no gift card products are created
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/products?is_giftcard=true' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The List Products endpoint accepts a variety of query parameters that can be used to filter the products. One of them is `is_giftcard`. When set to `true`, it will only retrieve the gift card products.
|
||||
|
||||
The request returns the `products` array in the response which holds the gift card in it, if it’s available. It also returns [pagination fields](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
### Create Gift Card Product
|
||||
|
||||
You can create a gift card product by sending a request to the [Create a Product](https://docs.medusajs.com/api/admin#products_postproducts) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
import { ProductStatus } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
medusa.admin.products.create({
|
||||
title: "My Gift Card",
|
||||
is_giftcard: true,
|
||||
discountable: false,
|
||||
status: ProductStatus.PUBLISHED,
|
||||
options: [
|
||||
{
|
||||
title: "Denomination",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "1",
|
||||
inventory_quantity: 0,
|
||||
manage_inventory: false,
|
||||
prices: [
|
||||
{
|
||||
amount: 2000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
value: "2000",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateProduct } from "medusa-react"
|
||||
import { ProductStatus } from "@medusajs/medusa"
|
||||
|
||||
const CreateGiftCard = () => {
|
||||
const createGiftCard = useAdminCreateProduct()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createGiftCard.mutate({
|
||||
title: "My Gift Card",
|
||||
is_giftcard: true,
|
||||
discountable: false,
|
||||
status: ProductStatus.PUBLISHED,
|
||||
options: [
|
||||
{
|
||||
title: "Denomination",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "1",
|
||||
inventory_quantity: 0,
|
||||
manage_inventory: false,
|
||||
prices: [
|
||||
{
|
||||
amount: 2000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
value: "2000",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateGiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/products`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: "My Gift Card",
|
||||
is_giftcard: true,
|
||||
discountable: false,
|
||||
status: "published",
|
||||
options: [
|
||||
{
|
||||
title: "Denomination",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "1",
|
||||
inventory_quantity: 0,
|
||||
manage_inventory: false,
|
||||
prices: [
|
||||
{
|
||||
amount: 2000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
value: "2000",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/products' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"title": "My Gift Card",
|
||||
"is_giftcard": true,
|
||||
"discountable": false,
|
||||
"status": "published",
|
||||
"options": [
|
||||
{
|
||||
"title": "Denomination"
|
||||
}
|
||||
],
|
||||
"variants": [
|
||||
{
|
||||
"title": "1",
|
||||
"inventory_quantity": 0,
|
||||
"manage_inventory": false,
|
||||
"prices": [
|
||||
{
|
||||
"amount": 2000,
|
||||
"currency_code": "usd"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"value": "2000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the `title` body parameter, which is the name given to the gift card. To add the gift card product, you need to supply the following parameters:
|
||||
|
||||
- `is_giftcard` set to `true`.
|
||||
- `discountable` set to `false`. It indicates that discounts don’t apply on this product.
|
||||
- `status`: a string indicating the status of the product. Can be `published`, `draft`, `proposed`, or `rejected`.
|
||||
- `options`: An array that includes available options of the product. For a gift card, you should add one option with the title “Denomination”.
|
||||
- `variants`: An array that includes the different variations of the product using the available options. For a gift card, you should pass each denomination value as an item in this array. The value is passed in the `prices` and `options` array. If you want to add prices for different currencies, you can pass them under `prices` and `options` as well.
|
||||
|
||||
You can pass other body parameters to change the handle, add images, and more. Check the [API reference](https://docs.medusajs.com/api/admin#products_postproducts) for available body parameters.
|
||||
|
||||
This request returns the created gift card product in the response.
|
||||
|
||||
:::tip
|
||||
|
||||
Although you can create an unlimited number of gift cards, the admin dashboard's UI only shows one gift card.
|
||||
|
||||
:::
|
||||
|
||||
### Update Gift Card Product
|
||||
|
||||
After creating a gift card, merchants can update it or its denomination.
|
||||
|
||||
You can update a gift card product’s details by sending a request to the [Update a Product](https://docs.medusajs.com/api/admin#products_postproductsproduct) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.products.update(giftCardId, {
|
||||
description: "The best gift card",
|
||||
})
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
|
||||
const UpdateGiftCard = () => {
|
||||
const createGiftCard = useAdminUpdateProduct(giftCardId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
createGiftCard.mutate({
|
||||
description: "The best gift card",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateGiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/products/${giftCardId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
description: "The best gift card",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/products/<GIFT_CARD_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"description": "The best gift card"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the gift card product as a path parameter. You can pass in its body parameters, any of the gift card’s properties to update.
|
||||
|
||||
In this example, you update the description of the gift card. You can check the [API reference](https://docs.medusajs.com/api/admin#products_postproductsproduct) for all the body parameters you can pass to this request.
|
||||
|
||||
This request returns the updated gift card product in the response.
|
||||
|
||||
### Delete Gift Card Product
|
||||
|
||||
You can delete a gift card product by sending a request to the [Delete a Product](https://docs.medusajs.com/api/admin#products_deleteproductsproduct) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.products.delete(giftCardId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteProduct } from "medusa-react"
|
||||
|
||||
const GiftCard = () => {
|
||||
const deleteGiftCard = useAdminDeleteProduct(giftCardId)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteGiftCard.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default GiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/products/${giftCardId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.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/products/<GIFT_CARD_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the gift card product as a path parameter.
|
||||
|
||||
It returns the following fields in the response:
|
||||
|
||||
- `id`: The ID of the deleted gift card.
|
||||
- `object`: A string indicating the type of object deleted. By default, its value is `product`.
|
||||
- `deleted`: A boolean value indicating whether the gift card was deleted or not.
|
||||
|
||||
---
|
||||
|
||||
## Manage Custom Gift Cards
|
||||
|
||||
This section covers how to manage custom gift cards. You can create an unlimited number of custom gift cards.
|
||||
|
||||
### List Custom Gift Cards
|
||||
|
||||
You can retrieve all custom gift cards by sending a request to the [List Gift Cards](https://docs.medusajs.com/api/admin#gift-cards_getgiftcards) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.giftCards.list()
|
||||
.then(({ gift_cards, limit, offset, count }) => {
|
||||
console.log(gift_cards.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { GiftCard } from "@medusajs/medusa"
|
||||
import { useAdminGiftCards } from "medusa-react"
|
||||
|
||||
const CustomGiftCards = () => {
|
||||
const { gift_cards, isLoading } = useAdminGiftCards()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{gift_cards && !gift_cards.length && (
|
||||
<span>No custom gift cards...</span>
|
||||
)}
|
||||
{gift_cards && gift_cards.length > 0 && (
|
||||
<ul>
|
||||
{gift_cards.map((giftCard: GiftCard) => (
|
||||
<li key={giftCard.id}>{giftCard.code}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomGiftCards
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/gift-cards`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ gift_cards, limit, offset, count }) => {
|
||||
console.log(gift_cards.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/gift-cards' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request doesn't require any parameters. It accepts parameters related to pagination, which you can check out in the [API reference](https://docs.medusajs.com/api/admin#gift-cards_getgiftcards).
|
||||
|
||||
This request returns an array of `gift_cards` and [pagination fields](https://docs.medusajs.com/api/admin#pagination) in the response.
|
||||
|
||||
### Create a Custom Gift Card
|
||||
|
||||
Merchants can create custom gift cards to send a reward or gift to the customer.
|
||||
|
||||
You can create a custom gift card by sending a request to the [Create a Gift Card](https://docs.medusajs.com/api/admin#gift-cards_postgiftcards) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.giftCards.create({
|
||||
region_id,
|
||||
value,
|
||||
})
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateGiftCard } from "medusa-react"
|
||||
|
||||
const CreateCustomGiftCards = () => {
|
||||
const createGiftCard = useAdminCreateGiftCard()
|
||||
// ...
|
||||
|
||||
const handleCreate = (regionId: string, value: number) => {
|
||||
createGiftCard.mutate({
|
||||
region_id: regionId,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateCustomGiftCards
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/gift-cards`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
value,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/gift-cards' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"region_id": "<REGION_ID>",
|
||||
"value": 2000
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the `region_id` body parameter. Its value should be the ID of the region that this gift card can be used in.
|
||||
|
||||
It optionally accepts other body parameters, including the `value` parameter, which is the amount of the gift card. You can check the [API reference](https://docs.medusajs.com/api/admin#gift-cards_postgiftcards) for the rest of the body parameters.
|
||||
|
||||
This request returns the created gift card object in the response.
|
||||
|
||||
### Update Custom Gift Card
|
||||
|
||||
Merchants can update any of the gift card’s properties, except for the value of the gift card.
|
||||
|
||||
You can update a gift card by sending a request to the [Update a Gift Card](https://docs.medusajs.com/api/admin#gift-cards_postgiftcardsgiftcard) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.giftCards.update(giftCardId, {
|
||||
balance,
|
||||
})
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateGiftCard } from "medusa-react"
|
||||
|
||||
const UpdateCustomGiftCards = () => {
|
||||
const updateGiftCard = useAdminUpdateGiftCard(
|
||||
customGiftCardId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = (regionId: string) => {
|
||||
updateGiftCard.mutate({
|
||||
region_id: regionId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateCustomGiftCards
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/gift-cards/${giftCardId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
balance,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/gift-cards/<GIFT_CARD_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"balance": 2000
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the gift card as a path parameter. It accepts in its body parameters the gift card’s properties that you want to update.
|
||||
|
||||
In this example, you update the balance of the gift card. The balance is the remaining amount in the gift card that the customer can use. You can check the [API reference](https://docs.medusajs.com/api/admin#gift-cards_postgiftcardsgiftcard) to learn what other parameters are allowed.
|
||||
|
||||
This request returns the updated gift card object in the response.
|
||||
|
||||
### Delete Custom Gift Card
|
||||
|
||||
You can delete a custom gift card by sending a request to the [Delete a Gift Card](https://docs.medusajs.com/api/admin#gift-cards_deletegiftcardsgiftcard) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.giftCards.delete(giftCardId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteGiftCard } from "medusa-react"
|
||||
|
||||
const CustomGiftCard = () => {
|
||||
const deleteGiftCard = useAdminDeleteGiftCard(
|
||||
customGiftCardId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteGiftCard.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CustomGiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/gift-cards/${giftCardId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.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/gift-card/<GIFT_CARD_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the gift card as a path parameter.
|
||||
|
||||
It returns the following fields in the response:
|
||||
|
||||
- `id`: The ID of the deleted gift card.
|
||||
- `object`: A string indicating the type of object deleted. By default, its value is `gift-card`.
|
||||
- `deleted`: A boolean value indicating whether the gift card was deleted or not.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use gift cards on the storefront](../storefront/use-gift-cards.mdx)
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
description: 'Learn how to send a gift card code to the customer. Once the customer purchases a gift card, an email can be sent with the code so that they can redeem the gift card.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Send Gift Card Code to Customer
|
||||
|
||||
In this document, you’ll learn how to send a customer the gift card code they purchased.
|
||||
|
||||
## Overview
|
||||
|
||||
Once the customer purchases a gift card, they should receive the code of the gift card so that they can use it in future purchases.
|
||||
|
||||
Typically, the code would be sent by email, however, you’re free to choose how you deliver the gift card code to the customer.
|
||||
|
||||
This document shows you how to track when a gift card has been purchased so that you can send its code to the customer.
|
||||
|
||||
:::tip
|
||||
|
||||
You can alternatively use the [SendGrid](../../../plugins/notifications/sendgrid.mdx) plugin, which handles sending the email automatically.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### Notification Provider
|
||||
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an existing plugin or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create a Subscriber
|
||||
|
||||
To subscribe to and handle an event, you must create a subscriber.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about subscribers in the [Subscribers](../../../development/events/subscribers.mdx) documentation.
|
||||
|
||||
:::
|
||||
|
||||
Create the file `src/subscribers/gift-card.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/gift-card.ts
|
||||
type InjectedDependencies = {
|
||||
// TODO add necessary dependencies
|
||||
}
|
||||
|
||||
class GiftCardSubscriber {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// TODO subscribe to event
|
||||
}
|
||||
}
|
||||
|
||||
export default GiftCardSubscriber
|
||||
```
|
||||
|
||||
You’ll be adding in the next step the necessary dependencies to the subscriber.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about [dependency injection](../../../development/fundamentals/dependency-injection.md) in this documentation.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Subscribe to the Event
|
||||
|
||||
In this step, you’ll subscribe to the event `gift_card.created` to send the customer a notification about their gift card.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
### Method 1: Using the NotificationService
|
||||
|
||||
If the notification provider you’re using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
|
||||
|
||||
```ts title=src/subscribers/gift-card.ts
|
||||
import { NotificationService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
notificationService: NotificationService
|
||||
}
|
||||
|
||||
class GiftCardSubscriber {
|
||||
constructor({ notificationService }: InjectedDependencies) {
|
||||
notificationService.subscribe(
|
||||
"gift_card.created",
|
||||
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default GiftCardSubscriber
|
||||
```
|
||||
|
||||
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, if you’re using SendGrid, the identifier is `sendgrid`.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
:::
|
||||
|
||||
### Method 2: Using the EventBusService
|
||||
|
||||
If the notification provider you’re using isn’t configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
|
||||
|
||||
```ts title=src/subscribers/gift-card.ts
|
||||
import {
|
||||
EventBusService,
|
||||
GiftCardService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
giftCardService: GiftCardService
|
||||
}
|
||||
|
||||
class GiftCardSubscriber {
|
||||
giftCardService: GiftCardService
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
giftCardService,
|
||||
}: InjectedDependencies) {
|
||||
this.giftCardService = giftCardService
|
||||
eventBusService.subscribe(
|
||||
"gift_card.created", this.handleGiftCard)
|
||||
}
|
||||
|
||||
handleGiftCard = async (data) => {
|
||||
const giftCard = await this.giftCardService.retrieve(
|
||||
data.id
|
||||
)
|
||||
// TODO send customer the gift card code
|
||||
}
|
||||
}
|
||||
|
||||
export default GiftCardSubscriber
|
||||
```
|
||||
|
||||
When using this method, you’ll have to handle the logic of sending the code to the customer inside the handler function, which in this case is `handleGiftCard`.
|
||||
|
||||
The `handleGiftCard` event receives a `data` object as a parameter. This object holds the `id` property which is the ID of the gift card. You can retrieve the full gift card object using the [GiftCardService](../../../references/services/classes/GiftCardService.md)
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
description: 'Learn what gift cards are and how they work in the Medusa backend. Learn about the relations between Gift Cards and other entities.'
|
||||
---
|
||||
|
||||
# Gift Cards
|
||||
|
||||
In this document, you’ll learn about Gift Cards and how they work in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Gift cards are products that customers can purchase and redeem in their future orders. Gift cards can have different amounts or denominations that a customer can choose from.
|
||||
|
||||
When a customer purchases a gift card, they should receive the code for the gift card by email or other type of notification. Then, they can use the code in their future purchases.
|
||||
|
||||
---
|
||||
|
||||
## Gift Cards as Products
|
||||
|
||||
Before a gift card is purchased, it’s essentially a `Product` entity. Each gift card can have unlimited denominations.
|
||||
|
||||
The gift card product has an attribute `is_giftcard` set to `true`. Its `options` property includes only one option, which is Denomination. The different denomination values are stored as `variants`.
|
||||
|
||||
Once the customer purchases a gift card product, it is transformed into a usable gift card represented by the [GiftCard entity](#giftcard-entity-overview).
|
||||
|
||||
---
|
||||
|
||||
## Custom Gift Cards
|
||||
|
||||
Aside from the gift card product, merchants can create usable gift cards and send directly to customers. These can be used as a reward sent to the customer or another form of discount.
|
||||
|
||||
As custom gift cards can be used once they’re created, they’re also represented by the [GiftCard entity](#giftcard-entity-overview).
|
||||
|
||||
---
|
||||
|
||||
## GiftCard Entity Overview
|
||||
|
||||
Some of the [GiftCard](../../references/entities/classes/GiftCard.md) entity’s attributes are:
|
||||
|
||||
- `code`: a unique string of random characters. This is the code that the customer can use during their checkout to redeem the gift card.
|
||||
- `value`: The amount of the gift card. This is the amount the customer purchased, or was gifted in the case of custom gift cards.
|
||||
- `balance`: The remaining amount of the gift card. If the customer uses the gift card on an order, and the order’s total does not exceed the amount available in the gift card, the remaining balance would be stored in this attribute. When the gift card is first created, `balance` and `value` have the same value.
|
||||
- `is_disabled`: A boolean value indicating whether a gift card is disabled or not.
|
||||
- `ends_at`: The expiry date and time of the gift card.
|
||||
- `tax_rate`: The tax rate applied when calculating the totals of an order. The tax rate’s value is determined based on the following conditions:
|
||||
- If the value of `region.gift_cards_taxable` is `false`, the `tax_rate` is `null`;
|
||||
- Otherwise, if the merchant or admin user has manually set the value of the tax rate, it is applied;
|
||||
- Otherwise, if the region has a tax rate, it’s applied on the gift card. If not, the value of the tax rate is `null`.
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
### Region
|
||||
|
||||
A gift card must belong to a region. When a customer purchases the gift card, the region they use to purchase the order is associated with the gift card.
|
||||
|
||||
For custom gift cards, the merchant specifies the region manually.
|
||||
|
||||
The ID of the region is stored in the attribute `region_id`. You can access the region by expanding the `region` relation and accessing `gift_card.region`.
|
||||
|
||||
### Order
|
||||
|
||||
If the gift card was created because the customer purchased it, it is associated with the placed order.
|
||||
|
||||
The ID of the order is stored in the attribute `order_id`. You can access the order by expanding the `order` relation and accessing `gift_card.order`.
|
||||
|
||||
You can also access the gift cards used in an order by expanding the `gift_cards` relation on the order and accessing `order.gift_cards`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Manage gift cards using admin APIs](./admin/manage-gift-cards.mdx)
|
||||
- [Use gift cards in the storefront](./storefront/use-gift-cards.mdx)
|
||||
- [Send the customer a gift card](./backend/send-gift-card-to-customer.md)
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
description: "Gift cards are prepaid codes that can be purchased and used during checkout as an alternative to payment methods or to receive a discount. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Gift Cards
|
||||
|
||||
Gift cards are prepaid codes that can be purchased and used during checkout as an alternative to payment methods or to receive a discount. This overview introduces the available features related to gift cards.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Gift Cards user guide](../../user-guide/gift-cards/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Gift Card Management
|
||||
|
||||
Admins can manage their main gift card with unlimited denominations.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/admin/manage-gift-cards',
|
||||
label: 'Admin: Manage Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage gift cards using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/gift-cards/manage',
|
||||
label: 'User Guide: Manage Discounts',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage gift cards using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Gift Card Purchasing
|
||||
|
||||
Customers can view the gift card and purchase it on the storefront. Customers can also use the gift card during checkout to deduct the gift card’s balance from the checkout total.
|
||||
|
||||
Developers can implement custom logic to send the gift card to the customer. They can alternatively implement a Shipping Profile that specifically handles the fulfillment of gift cards.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/backend/send-gift-card-to-customer',
|
||||
label: 'Backend: Send Gift Card Code',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use send a gift card code to a customer after their purchase.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/storefront/use-gift-cards',
|
||||
label: 'Storefront: Use Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to show gift cards to customers and allow them to redeem them.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Custom Gift Cards
|
||||
|
||||
Admins can create custom gift cards that are sent to specific customers. These customers can then use the gift card during checkout, the same way they would if they purchased one.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/gift-cards/custom',
|
||||
label: 'User Guide: Custom Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage custom gift cards using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/admin/manage-gift-cards#manage-custom-gift-cards',
|
||||
label: 'Admin: Manage Custom Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage custom gift cards using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/backend/send-gift-card-to-customer',
|
||||
label: 'Backend: Send Gift Card Code',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use send a custom gift card code to a customer.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how gift card related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards',
|
||||
label: 'Architecture: Gift Card',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Gift Card architecture.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Gift Card's relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Product',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'A store’s main gift card is essentially a Product.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Cart and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Gift Cards can be used during checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,322 @@
|
||||
---
|
||||
description: 'Learn how to implement gift-card related functionalities in your storefront using the REST APIs. This includes showing the gift card to customers and redeeming the gift card during checkout.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Use Gift Cards on the Storefront
|
||||
|
||||
In this document, you’ll learn how to use gift cards on a storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Customers can view and purchase gift card products. Then, customers can redeem the gift card in a future order.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to implement the following features in a storefront:
|
||||
|
||||
- Show gift card products to the customer.
|
||||
- View details of a gift card by its code.
|
||||
- Redeem a gift card during checkout.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
To use gift cards, you must have a gift card created first. You can follow this documentation to learn how to do it using the admin APIs.
|
||||
|
||||
In addition, this document doesn't cover how to implement checkout functionality. You can follow [this document](../../carts-and-checkout/storefront/implement-checkout-flow.mdx) to learn how to implement that.
|
||||
|
||||
---
|
||||
|
||||
## Show the Gift Card Product
|
||||
|
||||
Customers should be able to view gift cards before they make the purchase. Gift cards are essentially products like any other.
|
||||
|
||||
You can retrieve the gift card product using the [List Products](https://docs.medusajs.com/api/store#products_getproducts) endpoint, but passing it the `is_giftcard` query parameter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
is_giftcard: true,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
if (products.length) {
|
||||
// gift card products exist
|
||||
|
||||
} else {
|
||||
// no gift card products are created
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { useProducts } from "medusa-react"
|
||||
|
||||
const GiftCards = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
is_giftcard: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{products && !products.length && (
|
||||
<span>No Gift Cards</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCards
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?is_giftcard=true`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
if (products.length) {
|
||||
// gift card products exist
|
||||
} else {
|
||||
// no gift card products are created
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request does not require any parameters. You can pass query parameters to filter the returned products.
|
||||
|
||||
You can use the `is_giftcard` query parameter to retrieve only the gift card products by setting its value to `true`. To view other available parameters, check out the [API reference](https://docs.medusajs.com/api/store#products_getproducts)
|
||||
|
||||
The request returns the `products` array in the response, which holds the gift cards in it, if they're available. It also returns [pagination fields](https://docs.medusajs.com/api/store#pagination).
|
||||
|
||||
### Show Gift Card’s Denominations
|
||||
|
||||
The gift card’s denominations are available under the `variants` array. Each variant resembles a denomination.
|
||||
|
||||
The value of each denomination (or variant) is under the `prices` array. If you add the `region_id` query parameter, only prices for that specific regions are returned.
|
||||
|
||||
---
|
||||
|
||||
## View Details of a Gift Card by Code
|
||||
|
||||
After the customer purchases the gift card, they’ll receive a code to redeem that gift card. Using that code, the customer can also view the details of that gift card.
|
||||
|
||||
You can retrieve the details of a gift card by sending a request to the [Get Gift Card by Code](https://docs.medusajs.com/api/store#gift-cards_getgiftcardscode) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.giftCards.retrieve(code)
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
.catch((e) => {
|
||||
// gift card doesn't exist or is disabled
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useGiftCard } from "medusa-react"
|
||||
|
||||
const GiftCard = () => {
|
||||
const { gift_card, isLoading, isError } = useGiftCard("code")
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{gift_card && <span>{gift_card.value}</span>}
|
||||
{isError && <span>Gift Card does not exist</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/gift-cards/${code}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ gift_card }) => {
|
||||
console.log(gift_card.id)
|
||||
})
|
||||
.catch((e) => {
|
||||
// gift card doesn't exist or is disabled
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the code of the gift card passed as a path parameter.
|
||||
|
||||
It returns the gift card if it exists in the response. Otherwise, an error is returned.
|
||||
|
||||
### Show Gift Card Details
|
||||
|
||||
In the returned gift card object, the following details can be shown to the customer:
|
||||
|
||||
- `value`: The amount of the gift card.
|
||||
- `balance`: The remaining amount of the gift card. If the customer has previously used the gift card while purchasing an order but not its full value, this field shows how much is remaining in the card.
|
||||
- `ends_at`: The expiry date and time of the gift card.
|
||||
|
||||
You can learn what other properties are available in the returned gift card object in the [API reference](https://docs.medusajs.com/api/store#gift-cards_getgiftcardscode).
|
||||
|
||||
---
|
||||
|
||||
## Redeem Gift Card During Checkout
|
||||
|
||||
A customer can redeem more than one gift card during checkout. The cart’s totals will then be adjusted based on the applied gift card. The gift card’s amount isn’t actually used until the order is placed.
|
||||
|
||||
You can redeem a gift card during checkout by sending a request to the [Update Cart](https://docs.medusajs.com/api/store#carts_postcartscart) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
gift_cards: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.gift_cards.length)
|
||||
})
|
||||
.catch((e) => {
|
||||
// gift card doesn't exist or is disabled
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const setGiftCard = (code: string) => {
|
||||
updateCart.mutate({
|
||||
gift_cards: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/cart/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
gift_cards: [
|
||||
{
|
||||
code,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.gift_cards.length)
|
||||
})
|
||||
.catch((e) => {
|
||||
// gift card doesn't exist or is disabled
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the cart as a path parameter. It allows passing any cart property you want to update in its body parameters.
|
||||
|
||||
To add gift cards, you must pass the array `gift_cards`. Each item in the array should be an object having the property `code`, with its value being the code of the gift card to apply.
|
||||
|
||||
:::tip
|
||||
|
||||
The parameters passed in the update endpoint replace existing values. So, if you previously added a gift card, then tried adding another, you must include both in the `gift_cards` array.
|
||||
|
||||
:::
|
||||
|
||||
This request returns the card object in the response.
|
||||
|
||||
### Show Gift Card Details in the Cart
|
||||
|
||||
You can show the details of the applied gift cards by accessing `cart.gift_cards`. Its attributes are similar to those explained in [the previous section](#show-gft-card-details).
|
||||
|
||||
You can also use the following properties to display changes on the cart’s totals:
|
||||
|
||||
- `gift_card_total`: The total amount applied by all the gift cards.
|
||||
- `gift_card_tax_total`: The total tax applied for all gift cards.
|
||||
@@ -0,0 +1,781 @@
|
||||
---
|
||||
description: 'Learn how to manage inventory items using the admin APIs. This includes how to manage inventory items and inventory levels.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Inventory Items
|
||||
|
||||
In this document, you’ll learn how to manage inventory items using the admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the inventory items admin REST APIs, you can manage inventory items and inventory levels in your store.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage inventory items. This includes listing, creating, updating, and deleting items.
|
||||
- Manage inventory levels. This includes creating, updating, and deleting inventory levels.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
### Required Module
|
||||
|
||||
This guide assumes you have the Inventory module installed on your Medusa backend. If not, you can learn how to install it using [this guide](../install-modules.md#inventory-module).
|
||||
|
||||
Furthermore, inventory levels are tied to a location ID. So, it’s recommended to use the [Stock Location module](../install-modules.md#stock-location-module) if you don’t have any location logic implemented in place.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Inventory Items
|
||||
|
||||
You can list inventory items by sending a request to the [List Inventory Items endpoint](https://docs.medusajs.com/api/admin#inventory-items_getinventoryitems):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.list()
|
||||
.then(({ inventory_items }) => {
|
||||
console.log(inventory_items.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminInventoryItems } from "medusa-react"
|
||||
|
||||
function InventoryItems() {
|
||||
const {
|
||||
inventory_items,
|
||||
isLoading } = useAdminInventoryItems()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{inventory_items && !inventory_items.length && (
|
||||
<span>No Items</span>
|
||||
)}
|
||||
{inventory_items && inventory_items.length > 0 && (
|
||||
<ul>
|
||||
{inventory_items.map(
|
||||
(item) => (
|
||||
<li key={item.id}>{item.id}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InventoryItems
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_items }) => {
|
||||
console.log(inventory_items.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/inventory-items' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any path or query parameters. You can, however, pass path parameters to search or filter inventory items. For example, you can get inventory items in a specific location by passing the `location_id` query parameter. You can learn more about available query parameters in the [API reference](https://docs.medusajs.com/api/admin#inventory-items_getinventoryitems).
|
||||
|
||||
This request returns an array of inventory item objects.
|
||||
|
||||
---
|
||||
|
||||
## Create an Inventory Item
|
||||
|
||||
:::tip
|
||||
|
||||
Inventory items are automatically created when a variant is created with `manage_inventory` set to `true` or updated to enable the `manage_inventory` attribute. So, in general cases, you don’t need to create an inventory item manually.
|
||||
|
||||
:::
|
||||
|
||||
You can create an inventory item by sending a request to the [Create Inventory Item endpoint](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitems):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.create({
|
||||
variant_id,
|
||||
})
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateInventoryItem } from "medusa-react"
|
||||
|
||||
const CreateInventoryItem = () => {
|
||||
const createInventoryItem = useAdminCreateInventoryItem()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createInventoryItem.mutate({
|
||||
variant_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateInventoryItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variant_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/inventory-items' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"variant_id": "variant_123"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires in the body parameter the `variant_id` parameter, which is the ID of the variant to create this inventory item for. You can also pass other inventory-related parameters, such as `sku`. You can learn more about other available parameters in the [API reference](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitems).
|
||||
|
||||
This request returns the created inventory item as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Inventory Item
|
||||
|
||||
You can retrieve an inventory item by sending a request to the [Get Inventory Item endpoint](https://docs.medusajs.com/api/admin#inventory-items_getinventoryitemsinventoryitem):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.retrieve(inventoryItemId)
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminInventoryItem } from "medusa-react"
|
||||
|
||||
function InventoryItem() {
|
||||
const {
|
||||
inventory_item,
|
||||
isLoading } = useAdminInventoryItem(inventoryItemId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{inventory_item && (
|
||||
<span>{inventory_item.sku}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InventoryItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint accepts the ID of the inventory item as a path parameter. You can also path query parameters such as [expand](https://docs.medusajs.com/api/admin#expanding-fields) and [fields](https://docs.medusajs.com/api/admin#selecting-fields).
|
||||
|
||||
The request returns the inventory item as an object.
|
||||
|
||||
---
|
||||
|
||||
## Update Inventory Item
|
||||
|
||||
You can update an inventory item by sending a request to the [Update Inventory Item endpoint](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitemsinventoryitem):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.update(inventoryItemId, {
|
||||
origin_country: "US",
|
||||
})
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateInventoryItem } from "medusa-react"
|
||||
|
||||
const UpdateInventoryItem = () => {
|
||||
const updateInventoryItem = useAdminUpdateInventoryItem(
|
||||
inventoryItemId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateInventoryItem.mutate({
|
||||
origin_country: "US",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateInventoryItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin_country: "US",
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"origin_country": "US"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the inventory item’s ID as a path parameter. You can pass any of the inventory item’s attributes that you want to update in its body parameter. The example above updates the value of the `origin_country` attribute. You can learn more about available body parameters in the [API reference](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitemsinventoryitem).
|
||||
|
||||
The request returns the updated inventory item as an object.
|
||||
|
||||
---
|
||||
|
||||
## Manage Inventory levels
|
||||
|
||||
This section shows you the different ways you can manage inventory levels. Each location level is associated with an inventory item.
|
||||
|
||||
### List inventory levels
|
||||
|
||||
You can list inventory levels of an inventory item by sending a request to the [List inventory levels endpoint](https://docs.medusajs.com/api/admin#inventory-items_getinventoryitemsinventoryitemlocationlevels):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.listLocationLevels(inventoryItemId)
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.location_levels)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminInventoryItemLocationLevels,
|
||||
} from "medusa-react"
|
||||
|
||||
function InventoryItem() {
|
||||
const {
|
||||
inventory_item,
|
||||
isLoading,
|
||||
} = useAdminInventoryItemLocationLevels(inventoryItemId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{inventory_item && (
|
||||
<ul>
|
||||
{inventory_item.location_levels.map((level) => (
|
||||
<span key={level.id}>{level.stocked_quantity}</span>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InventoryItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}/location-levels`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.location_levels)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>/location-levels' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the inventory item as a path parameter. You can also pass [expand](https://docs.medusajs.com/api/admin#expanding-fields) and [fields](https://docs.medusajs.com/api/admin#selecting-fields) query parameters.
|
||||
|
||||
The request returns the inventory item as an object. In that object, the list of inventory levels are available under the property `location_levels`.
|
||||
|
||||
### Create Inventory Level
|
||||
|
||||
You can create a location level by sending a request to the [Create Inventory Level endpoint](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitemsinventoryitemlocationlevels):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.createLocationLevel(
|
||||
inventoryItemId,
|
||||
{
|
||||
location_id,
|
||||
stocked_quantity: 10,
|
||||
}
|
||||
)
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateLocationLevel } from "medusa-react"
|
||||
|
||||
const CreateLocationLevel = () => {
|
||||
const createLocationLevel = useAdminCreateLocationLevel(
|
||||
inventoryItemId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createLocationLevel.mutate({
|
||||
location_id,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateLocationLevel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}/location-levels`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
location_id,
|
||||
stocked_quantity: 10,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>/location-levels' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"location_id": "<LOCATION_ID>",
|
||||
"stocked_quantity": 10
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the inventory item ID as a path parameter. In the request body, it requires the following parameters:
|
||||
|
||||
- `location_id`: The ID of the location associated with this location level. This ID is typically available through using the stock location module.
|
||||
- `stocked_quantity`: A number indicating the item’s quantity in stock.
|
||||
|
||||
You can also pass other optional request body parameters, as explained in the [API reference](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitemsinventoryitemlocationlevels).
|
||||
|
||||
This request returns the inventory item associated with the created location level.
|
||||
|
||||
### Update Location Level
|
||||
|
||||
You can update a location level by sending a request to the [Update Location Level endpoint](https://docs.medusajs.com/api/admin#inventory-items_postinventoryitemsinventoryitemlocationlevelslocationlevel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.updateLocationLevel(
|
||||
inventoryItemId,
|
||||
locationId,
|
||||
{
|
||||
stocked_quantity: 15,
|
||||
}
|
||||
)
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateLocationLevel } from "medusa-react"
|
||||
|
||||
const UpdateLocationLevel = () => {
|
||||
const updateLocationLevel = useAdminUpdateLocationLevel(
|
||||
inventoryItemId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateLocationLevel.mutate({
|
||||
stockLocationId,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateLocationLevel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stocked_quantity: 10,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>/location-levels/<LOCATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"stocked_quantity": 10
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires two path parameters: the first one being the ID of the inventory item, and the second one being the ID of the location.
|
||||
|
||||
In the body, you can optionally pass any of the location level’s attributes to update. In the example above, you update the `stocked_quantity` attribute of the location level.
|
||||
|
||||
The request returns the inventory item associated with the location level as an object.
|
||||
|
||||
### Delete Location Level
|
||||
|
||||
You can delete a location level of an inventory item by sending a request to the [Delete Location Level endpoint](https://docs.medusajs.com/api/admin#inventory-items_deleteinventoryitemsinventoryitelocationlevelslocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.deleteLocationLevel(
|
||||
inventoryItemId,
|
||||
locationId
|
||||
)
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteLocationLevel } from "medusa-react"
|
||||
|
||||
const DeleteLocationLevel = () => {
|
||||
const deleteLocationLevel = useAdminDeleteLocationLevel(
|
||||
inventoryItemId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteLocationLevel.mutate(locationId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteLocationLevel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ inventory_item }) => {
|
||||
console.log(inventory_item.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/inventory-items/<ITEM_ID>/location-levels/<LOC_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires two path parameters: the first one being the inventory item’s ID and the second one being the location level’s ID.
|
||||
|
||||
The request returns the inventory item as an object.
|
||||
|
||||
---
|
||||
|
||||
## Delete Inventory Item
|
||||
|
||||
You can delete an inventory item by sending a request to the [Delete Inventory Item endpoint](https://docs.medusajs.com/api/admin#inventory-items_deleteinventoryitemsinventoryitem):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.inventoryItems.delete(inventoryItemId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteInventoryItem } from "medusa-react"
|
||||
|
||||
const DeleteInventoryItem = () => {
|
||||
const deleteInventoryItem = useAdminDeleteInventoryItem(
|
||||
inventoryItemId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteInventoryItem.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteInventoryItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/inventory-items/${inventoryItemId}`,
|
||||
{
|
||||
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/inventory-items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the inventory item’s ID be passed as a path parameter.
|
||||
|
||||
It returns the following fields in the response:
|
||||
|
||||
- `id`: The ID of the inventory item.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `inventory_item`.
|
||||
- `deleted`: A boolean value indicating whether the inventory item was deleted successfully.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage stock locations](./manage-stock-locations.mdx)
|
||||
- [How to manage item allocations in orders](./manage-item-allocations-in-orders.mdx)
|
||||
@@ -0,0 +1,689 @@
|
||||
---
|
||||
description: 'Learn how to manage item allocations in orders using the admin APIs. This includes how to manage reservations and specify a location for fulfillments and returns.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Item Allocations in Orders
|
||||
|
||||
In this document, you’ll learn how to manage item allocations in orders using the admin APIs.
|
||||
|
||||
:::note
|
||||
|
||||
The terms “item allocation” and “reservation” are synonymous and are used interchangeably throughout this guide.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
When an order is placed, the ordered quantity is reserved from the stocked quantity of each product variant having `manage_inventory` enabled. The reserved quantity is allocated to a random location associated with the order’s sales channel, but can also be changed afterward.
|
||||
|
||||
When a fulfillment is created, a location can be specified to fulfill the item from. This will deduct the stocked quantity and reset the reserved quantity in the chosen location based on the quantity chosen for fulfillment.
|
||||
|
||||
When a return is requested, a location can be specified to return the item to. The will increment the stocked quantity in the chosen location based on the returned quantity.
|
||||
|
||||

|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage an item allocation for any resource, and not just for an item in an order. This includes listing, updating, or deleting item allocation.
|
||||
- Specify location when creating fulfillment.
|
||||
- Specify location when creating a return. This also applies to returns that are part of a swap or a claim.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
### Required Module
|
||||
|
||||
This guide assumes you have a stock location and inventory modules installed. You can use Medusa’s [Stock Location and Inventory modules](../install-modules.md) or create your own modules.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Manage Item Allocations in an Order
|
||||
|
||||
Using the reservations admin REST APIs, you can create an item allocation or reservation for any resource, and not just items in an order. This gives you more options in how you handle item allocations.
|
||||
|
||||
In this guide, however, the focus will be on how to use these endpoints for order-related functionalities.
|
||||
|
||||
### Create Item Allocation
|
||||
|
||||
Item allocations are created automatically for items that are associated with product variants having the `manage_inventory` attribute enabled. You typically don’t need to create an item allocation, unless you delete the previous item allocation.
|
||||
|
||||
You can create an item allocation by sending a request to the [Create a Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_postreservations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.create({
|
||||
line_item_id,
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
})
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateReservation } from "medusa-react"
|
||||
|
||||
const CreateReservation = () => {
|
||||
const createReservation = useAdminCreateReservation()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createReservation.mutate({
|
||||
line_item_id,
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
line_item_id,
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/reservations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"line_item_id": "<LINE_ITEM_ID>",
|
||||
"location_id": "<LOC_ID>",
|
||||
"inventory_item_id": "<INV_ITEM_ID>",
|
||||
"quantity": 1
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following body parameters:
|
||||
|
||||
- `line_item_id`: The ID of the order’s line item the allocation is being created for.
|
||||
- `location_id`: The ID of the location the item is being allocated from.
|
||||
- `inventory_item_id`: The ID of the inventory item the line item’s variant is associated with.
|
||||
- `quantity`: The quantity to allocate.
|
||||
|
||||
The request returns the created reservation as an object.
|
||||
|
||||
### List Item Allocations
|
||||
|
||||
When listing item allocations, by default, you’ll be retrieving all item allocations in your commerce system. You can, however, provide optional fields to filter the item allocations retrieved.
|
||||
|
||||
You can retrieve the item allocations of a line item in an order using the [List Reservations endpoint](https://docs.medusajs.com/api/admin#reservations_getreservations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.list({
|
||||
line_item_id,
|
||||
})
|
||||
.then(({ reservations, count, limit, offset }) => {
|
||||
console.log(reservations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminReservations } from "medusa-react"
|
||||
|
||||
function Reservations() {
|
||||
const { reservations, isLoading } = useAdminReservations({
|
||||
line_item_id,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{reservations && !reservations.length && (
|
||||
<span>No Reservations</span>
|
||||
)}
|
||||
{reservations && reservations.length > 0 && (
|
||||
<ul>
|
||||
{reservations.map((reservation) => (
|
||||
<li key={reservation.id}>{reservation.quantity}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Reservations
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations?line_item_id=${lineItemId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservations, count, limit, offset }) => {
|
||||
console.log(reservations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/reservations?line_item_id=<LINE_ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any path or query parameters. As mentioned earlier, you can pass query parameters to filter the reservations. In the code snippets above, you filter the reservations by a line item ID. You can, however, filter by other attributes, such as the ID of the location. You can refer to the [API reference](https://docs.medusajs.com/api/admin#reservations_getreservations) for a full list of query parameters.
|
||||
|
||||
The request returns the reservations along with [pagination fields](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
### Retrieve Item Allocation
|
||||
|
||||
You can retrieve a single item allocation by its ID using the [Get a Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_getreservationsreservation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.retrieve(reservationId)
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminReservation } from "medusa-react"
|
||||
|
||||
function Reservation() {
|
||||
const {
|
||||
reservation,
|
||||
isLoading } = useAdminReservation(reservationId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{reservation && (
|
||||
<span>{reservation.quantity}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Reservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations/${reservationId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/reservations/<RESERVATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the reservation’s ID as a path parameter.
|
||||
|
||||
The request returns the reservation as an object.
|
||||
|
||||
### Update Item Allocation
|
||||
|
||||
You can update an item allocation to change the location to allocate from or the quantity to allocate by sending a request to the [Update Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_postreservationsreservation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.update(reservationId, {
|
||||
quantity,
|
||||
})
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateReservation } from "medusa-react"
|
||||
|
||||
const UpdateReservation = () => {
|
||||
const updateReservation = useAdminUpdateReservation(
|
||||
reservationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
updateReservation.mutate({
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations/${reservationId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/reservations/<RESERVATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 3
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the reservation as a path parameter.
|
||||
|
||||
In the request body parameters, you can optionally pass any of the following parameters to make updates to the reservation:
|
||||
|
||||
- `quantity`: The quantity that should be reserved.
|
||||
- `location_id`: The ID of the location that the item should be allocated from.
|
||||
- `metadata`: set or change the reservation’s metadata.
|
||||
|
||||
The request returns the updated reservation as an object.
|
||||
|
||||
### Delete Item Allocation
|
||||
|
||||
Deleting an item allocation means that the quantity that was previously reserved is no longer reserved.
|
||||
|
||||
You can delete an item allocation by sending a request to the [Delete Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_deletereservationsreservation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.delete(reservationId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteReservation } from "medusa-react"
|
||||
|
||||
const DeleteReservation = () => {
|
||||
const deleteReservation = useAdminDeleteReservation(
|
||||
reservationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteReservation.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations/${reservationId}`, {
|
||||
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/reservations/<RESERVATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the reservation ID to be passed as a path parameter.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the reservation.
|
||||
- `object`: The type of object that was removed. In this case, the value will be `reservation`.
|
||||
- `deleted`: A boolean value indicating whether the reservation was successfully deleted.
|
||||
|
||||
---
|
||||
|
||||
## Specify Location when Creating Fulfillment
|
||||
|
||||
When you create a fulfillment of an order, you can specify the location to fulfill the item from by passing the `location_id` parameter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.createFulfillment(orderId, {
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
// ...other parameters
|
||||
location_id,
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateFulfillment } from "medusa-react"
|
||||
|
||||
const CreateFulfillment = () => {
|
||||
const createFulfillment = useAdminCreateFulfillment(orderId)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createFulfillment.mutate({
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
// ...other parameters
|
||||
location_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateFulfillment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/fulfillment`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
// ...other parameters
|
||||
location_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/fulfillment' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"items": [
|
||||
{
|
||||
"item_id": "<LINE_ITEM_ID>",
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"location_id": "<LOC_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The `location_id` is an optional parameter that allows you to specify where to fulfill the item from. This subsequently decrements the stock quantity of the product variant in that location.
|
||||
|
||||
You can learn more about this endpoint’s parameters and response in the [API reference](https://docs.medusajs.com/api/admin#orders_postordersorderfulfillments).
|
||||
|
||||
---
|
||||
|
||||
## Specify Location when Requesting Return
|
||||
|
||||
When requesting a return, you can specify the location to return the item to by passing the `location_id` parameter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.requestReturn(orderId, {
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
// other parameters...
|
||||
location_id,
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminRequestReturn } from "medusa-react"
|
||||
|
||||
const RequestReturn = () => {
|
||||
const requestReturn = useAdminRequestReturn(orderId)
|
||||
// ...
|
||||
|
||||
const handleRequest = () => {
|
||||
requestReturn.mutate({
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
// ...other parameters
|
||||
location_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default RequestReturn
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/return`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
// ...other parameters
|
||||
location_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/return' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"items": [
|
||||
{
|
||||
"item_id": "<LINE_ITEM_ID>",
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"location_id": "<LOC_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The `location_id` is an optional parameter that allows you to specify where to return the item to. This subsequently increments the stock quantity of the product variant in that location.
|
||||
|
||||
You can learn more about this endpoint’s parameters and response in [the API reference](https://docs.medusajs.com/api/admin#orders_postordersorderreturns).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage inventory items](./manage-inventory-items.mdx)
|
||||
- [How to manage stock locations](./manage-stock-locations.mdx)
|
||||
@@ -0,0 +1,401 @@
|
||||
---
|
||||
description: 'Learn how to manage custom reservations of a product variant using the admin APIs. This includes how to list, create, update, and delete reservations.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Custom Reservations
|
||||
|
||||
In this document, you’ll learn how to manage custom reservations of a product variant using the admin APIs.
|
||||
|
||||
:::tip
|
||||
|
||||
Although this guide covers how to manage custom reservations of product variants, you can create custom reservations for any entity that is associated with an Inventory Item.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
When an order is created, reservations are created for the items in that order automatically. However, Medusa also provides the capability to create custom reservations that aren’t related to any order. Custom reservations allow you to allocate quantities of a product variant manually. You can do that using the Reservations admin APIs.
|
||||
|
||||
The functionalities in this guide apply to all types of reservations, including those associated with an order and those that aren’t.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List reservations, including custom reservations or those associated with orders.
|
||||
- Manage a reservation, including creating, updating, and deleting a reservation.
|
||||
|
||||
:::note
|
||||
|
||||
You can check the [API reference](https://docs.medusajs.com/api/admin) for all available Reservations endpoints.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
### Required Module
|
||||
|
||||
This guide assumes you have a stock location and inventory modules installed. You can use Medusa’s [Stock Location and Inventory modules](../install-modules.md) or create your own modules.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Reservations
|
||||
|
||||
You can list all reservations in your store by sending a request to the [List Reservations endpoint](https://docs.medusajs.com/api/admin#reservations_getreservations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.list()
|
||||
.then(({ reservations, limit, count, offset }) => {
|
||||
console.log(reservations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminReservations } from "medusa-react"
|
||||
|
||||
const Reservations = () => {
|
||||
const { reservations, isLoading } = useAdminReservations()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{reservations && !reservations.length && (
|
||||
<span>No Reservations</span>
|
||||
)}
|
||||
{reservations && reservations.length > 0 && (
|
||||
<ul>
|
||||
{reservations.map((reservation) => (
|
||||
<li key={reservation.id}>{reservation.quantity}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Reservations
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservations, limit, count, offset }) => {
|
||||
console.log(reservations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/reservations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any query or path parameters. You can pass it query parameters for filtering or pagination purposes. Check out the [API reference](https://docs.medusajs.com/api/admin#reservations_getreservations) for a list of accepted query parameters.
|
||||
|
||||
This endpoint returns an array of reservations along with [pagination parameters](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
---
|
||||
|
||||
## Create a Reservation
|
||||
|
||||
:::note
|
||||
|
||||
Before you create a reservation for a product variant, make sure you’ve created an inventory item for that variant. You can learn how to do that in [this guide](./manage-inventory-items.mdx#create-an-inventory-item).
|
||||
|
||||
:::
|
||||
|
||||
You can create a reservation by sending a request to the [Create Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_postreservations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.create({
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
})
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateReservation } from "medusa-react"
|
||||
|
||||
const CreateReservation = () => {
|
||||
const createReservation = useAdminCreateReservation()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createReservation.mutate({
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
location_id,
|
||||
inventory_item_id,
|
||||
quantity,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/reservations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"location_id": "<LOC_ID>",
|
||||
"inventory_item_id": "<INV_ITEM_ID>",
|
||||
"quantity": 1
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following body parameters:
|
||||
|
||||
- `location_id`: The ID of the location the reservation is created in.
|
||||
- `inventory_item_id`: The ID of the inventory item the product variant is associated with.
|
||||
- `quantity`: The quantity to allocate.
|
||||
|
||||
The request returns the created reservation as an object.
|
||||
|
||||
---
|
||||
|
||||
## Update a Reservation
|
||||
|
||||
You can update a reservation by sending a request to the [Update Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_postreservationsreservation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.update(reservationId, {
|
||||
quantity,
|
||||
})
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateReservation } from "medusa-react"
|
||||
|
||||
const UpdateReservation = () => {
|
||||
const updateReservation = useAdminUpdateReservation(
|
||||
reservationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
updateReservation.mutate({
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations/${reservationId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ reservation }) => {
|
||||
console.log(reservation.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/reservations/<RESERVATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 3
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the reservation as a path parameter.
|
||||
|
||||
In the request body parameters, you can optionally pass any of the following parameters to make updates to the reservation:
|
||||
|
||||
- `quantity`: The quantity that should be reserved.
|
||||
- `location_id`: The ID of the location that the product variant should be allocated from.
|
||||
- `metadata`: set or change the reservation’s metadata.
|
||||
|
||||
The request returns the updated reservation as an object.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Reservation
|
||||
|
||||
You can delete a reservation by sending a request to the [Delete Reservation endpoint](https://docs.medusajs.com/api/admin#reservations_deletereservationsreservation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.reservations.delete(reservationId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteReservation } from "medusa-react"
|
||||
|
||||
const DeleteReservation = () => {
|
||||
const deleteReservation = useAdminDeleteReservation(
|
||||
reservationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteReservation.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteReservation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/reservations/${reservationId}`, {
|
||||
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/reservations/<RESERVATION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the reservation ID to be passed as a path parameter.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the reservation.
|
||||
- `object`: The type of object that was removed. In this case, the value will be `reservation`.
|
||||
- `deleted`: A boolean value indicating whether the reservation was successfully deleted.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage inventory items](./manage-inventory-items.mdx)
|
||||
- [How to manage stock locations](./manage-stock-locations.mdx)
|
||||
@@ -0,0 +1,620 @@
|
||||
---
|
||||
description: 'Learn how to manage stock locations using the admin APIs. This includes how to list, create, update, and delete stock locations.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Stock Locations
|
||||
|
||||
In this document, you’ll learn how to manage stock locations using the admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the stock locations admin REST APIs, you can manage stock locations in your store, including creating, updating, and deleting locations.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List stock locations.
|
||||
- Create a stock location.
|
||||
- Retrieve a stock location.
|
||||
- Associate a stock location with a sales channel, and remove the association.
|
||||
- Update a stock location.
|
||||
- Delete a stock location.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
### Required Module
|
||||
|
||||
This guide assumes you have a stock location module installed. You can use Medusa’s [Stock Location module](../install-modules.md#stock-location-module) or [create your own module](../backend/create-stock-location-service.md).
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Stock Locations
|
||||
|
||||
You can list stock locations by using the [List Stock Locations endpoint](https://docs.medusajs.com/api/admin#stock-locations_getstocklocations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.stockLocations.list()
|
||||
.then(({ stock_locations, limit, offset, count }) => {
|
||||
console.log(stock_locations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminStockLocations } from "medusa-react"
|
||||
|
||||
function StockLocations() {
|
||||
const {
|
||||
stock_locations,
|
||||
isLoading } = useAdminStockLocations()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{stock_locations && !stock_locations.length && (
|
||||
<span>No Locations</span>
|
||||
)}
|
||||
{stock_locations && stock_locations.length > 0 && (
|
||||
<ul>
|
||||
{stock_locations.map(
|
||||
(location) => (
|
||||
<li key={location.id}>{location.name}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StockLocations
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/stock-locations`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ stock_locations, limit, offset, count }) => {
|
||||
console.log(stock_locations.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/stock-locations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any path or query parameters. You can, however, pass it query parameters to search or filter the list of stock locations. For example, you can pass the `q` query parameter to search through the locations by name. You can learn about available query parameters in the [API reference](https://docs.medusajs.com/api/admin#stock-locations_getstocklocations).
|
||||
|
||||
The request returns an array of stock location objects along with [pagination parameters](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
---
|
||||
|
||||
## Create a Stock Location
|
||||
|
||||
You can create a stock location using the [Create a Stock Location endpoint](https://docs.medusajs.com/api/admin#stock-locations_poststocklocations):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.stockLocations.create({
|
||||
name: "Main Warehouse",
|
||||
})
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateStockLocation } from "medusa-react"
|
||||
|
||||
const CreateStockLocation = () => {
|
||||
const createStockLocation = useAdminCreateStockLocation()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createStockLocation.mutate({
|
||||
name: "Main Warehouse",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateStockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/stock-locations`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Main Warehouse",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/stock-locations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Main Warehouse"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires in its body parameters the `name` field, which is the name of the stock location. You can also pass in the request body parameters other fields related to the address or metadata. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#stock-locations_poststocklocations).
|
||||
|
||||
This request returns the created stock location as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Stock Location
|
||||
|
||||
You can retrieve a stock location by sending a request to the [Get Stock Location endpoint](https://docs.medusajs.com/api/admin#stock-locations_getstocklocationsstocklocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.stockLocations.retrieve(stockLocationId)
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminStockLocation } from "medusa-react"
|
||||
|
||||
function StockLocation() {
|
||||
const {
|
||||
stock_location,
|
||||
isLoading } = useAdminStockLocation(stockLocationId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{stock_location && (
|
||||
<span>{stock_location.name}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/stock-locations/${stockLocationId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/stock-locations/<LOC_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the stock location ID to be passed as a path parameter. It also accepts query parameters related to expanding and selecting fields. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#stock-locations_getstocklocationsstocklocation).
|
||||
|
||||
It returns the stock location as an object.
|
||||
|
||||
---
|
||||
|
||||
## Associate a Stock Location with a Sales Channel
|
||||
|
||||
You can associate a stock location with a sales channel by sending a request to the [Associate Stock Channel endpoint](https://docs.medusajs.com/api/admin#sales-channels_postsaleschannelssaleschannelstocklocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.addLocation(salesChannelId, {
|
||||
location_id,
|
||||
})
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminAddLocationToSalesChannel } from "medusa-react"
|
||||
|
||||
function StockLocation() {
|
||||
const addLocation = useAdminAddLocationToSalesChannel()
|
||||
// ...
|
||||
|
||||
const handleAdd = () => {
|
||||
addLocation.mutate({
|
||||
sales_channel_id,
|
||||
location_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default StockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels/${salesChannelId}/stock-locations`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
location_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/sales-channels/<CHANNEL_ID>/stock-locations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"location_id": "<LOC_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the sales channel as a path parameter. In its body parameters, it requires the ID of the stock location you’re associating the sales channel with.
|
||||
|
||||
This request returns the sales channel object.
|
||||
|
||||
:::note
|
||||
|
||||
You can associate a location with more than one sales channel, and you can associate a sales channel with more than one location.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Remove Association Between Stock Location and Sales Channel
|
||||
|
||||
You can remove the association between a stock location and a sales channel by sending a request to the [Remove Stock Location Association endpoint](https://docs.medusajs.com/api/admin#sales-channels_deletesaleschannelssaleschannelstocklocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.removeLocation(salesChannelId, {
|
||||
location_id,
|
||||
})
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminRemoveLocationFromSalesChannel,
|
||||
} from "medusa-react"
|
||||
|
||||
function StockLocation() {
|
||||
const removeLocation =
|
||||
useAdminRemoveLocationFromSalesChannel()
|
||||
// ...
|
||||
|
||||
const handleRemove = () => {
|
||||
removeLocation.mutate({
|
||||
sales_channel_id,
|
||||
location_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default StockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels/${salesChannelId}/stock-locations`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
location_id,
|
||||
}),
|
||||
})
|
||||
.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/sales-channels/<CHANNEL_ID>/stock-locations' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"location_id": "<LOC_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the sales channel as a path parameter. In its body parameters, it requires the ID of the stock location you’re removing the association of the sales channel with.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the location.
|
||||
- `object`: The type of object that was removed. In this case, the value will be `stock-location`.
|
||||
- `deleted`: A boolean value indicating whether the association with the stock location was removed.
|
||||
|
||||
:::note
|
||||
|
||||
This request does not delete the stock location. It only removes the association between it and the specified sales channel.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update a Stock Location
|
||||
|
||||
You can update a stock location by sending a request to the [Update Stock Location endpoint](https://docs.medusajs.com/api/admin#stock-locations_poststocklocationsstocklocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.stockLocations.update(stockLocationId, {
|
||||
name: "Warehouse",
|
||||
})
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateStockLocation } from "medusa-react"
|
||||
|
||||
function StockLocation() {
|
||||
const updateLocation = useAdminUpdateStockLocation(
|
||||
stockLocationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleRemove = () => {
|
||||
updateLocation.mutate({
|
||||
name: "Warehouse",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default StockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/stock-locations/${stockLocationId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Warehouse",
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ stock_location }) => {
|
||||
console.log(stock_location.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/stock-locations/<LOC_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Main Warehouse"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of a stock location as a path parameter. In its body parameters, you can pass any of the location’s attributes to update, such as the name or address. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#stock-locations_poststocklocationsstocklocation).
|
||||
|
||||
This request returns the updated stock location as an object.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Stock Location
|
||||
|
||||
You can delete a stock location by sending a request to the [Delete Stock Location endpoint](https://docs.medusajs.com/api/admin#stock-locations_deletestocklocationsstocklocation):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.stockLocations.delete(stockLocationId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteStockLocation } from "medusa-react"
|
||||
|
||||
function StockLocation() {
|
||||
const deleteLocation = useAdminDeleteStockLocation(
|
||||
stockLocationId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteLocation.mutate()
|
||||
}
|
||||
}
|
||||
|
||||
export default StockLocation
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/stock-locations/${stockLocationId}`,
|
||||
{
|
||||
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/stock-locations/<LOC_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the stock location as a path parameter.
|
||||
|
||||
It returns the following fields in the response:
|
||||
|
||||
- `id`: The ID of the location.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `stock_location`.
|
||||
- `deleted`: A boolean value indicating whether the stock location was deleted successfully.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage inventory items](./manage-inventory-items.mdx)
|
||||
- [How to manage item allocations in orders](./manage-item-allocations-in-orders.mdx)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,384 @@
|
||||
---
|
||||
description: 'Learn how to create a stock location service, which you can use in a custom stock location module in the Medusa backend.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Stock Location Service
|
||||
|
||||
In this document, you’ll learn how to create a stock location service, which you can use in a custom stock location module in the Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
A stock location module is used in a commerce application, such as the Medusa backend, to handle functionalities related to the different locations a stock-kept item can be located in.
|
||||
|
||||
While Medusa provides a [stock-location module](../stock-location-module.md) that you can use in your Medusa backend, you can also create a custom module to handle these functionalities.
|
||||
|
||||
The module is expected to, at the very least, export the stock-location service. If necessary, you can include entities, migrations, and other resources as part of the export.
|
||||
|
||||
This guide will only explain what is required to create in your custom stock location service, and not the entities or other resources, as those you have the freedom to choose how to implement. You can refer to the [Modules documentation](../../../development/modules/create.mdx) for other details on how to create and use the module.
|
||||
|
||||
:::note
|
||||
|
||||
It should be noted that the Medusa backend expects the stock location module to have entities for a location and a location address, as it uses the IDs of those entities when orchestrating between different modules and the in the endpoints it exposes. You can learn more about this in the [Stock Location Module Architecture documentation](../stock-location-module.md).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The `IStockLocationService` interface that you’ll be implementing is available in the `@medusajs/types` package. So, make sure to install it in your Medusa backend or the module project (depending on where you’re adding your implementation):
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/types
|
||||
```
|
||||
|
||||
You’ll also be using decorators in your methods that are imported from the `@medusajs/utils` package, so make sure to install it as well:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/utils
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Implement the Stock Location Service
|
||||
|
||||
Create a file in the `src/services` directory that will hold your custom stock location service. For example, `src/services/stock-location.ts`.
|
||||
|
||||
In that file, add the following content:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
import {
|
||||
CreateStockLocationInput,
|
||||
FilterableStockLocationProps,
|
||||
FindConfig,
|
||||
IStockLocationService,
|
||||
SharedContext,
|
||||
StockLocationDTO,
|
||||
UpdateStockLocationInput,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectEntityManager,
|
||||
MedusaContext,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
class StockLocationService implements IStockLocationService {
|
||||
async list(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async listAndCount(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<[StockLocationDTO[], number]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async create(
|
||||
input: CreateStockLocationInput,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async update(
|
||||
id: string,
|
||||
input: UpdateStockLocationInput,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async delete(
|
||||
id: string,
|
||||
context?: SharedContext | undefined
|
||||
): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default StockLocationService
|
||||
```
|
||||
|
||||
This defines the class `StockLocationService` that implements the `IStockLocationService` service imported from the `@medusajs/types` package.
|
||||
|
||||
The following sections explain the different methods you need to implement.
|
||||
|
||||
### Using Method Decorators
|
||||
|
||||
For each of the methods, you’ll be using the following decorators:
|
||||
|
||||
1. `@InjectEntityManager`: Ensures that a transaction entity manager is always passed to the method. The transaction manager is useful when performing database operations.
|
||||
2. `@MedusaContext`: Used on a parameter passed to a method having the `@InjectEntityManager` decorator. It indicates which parameter should include the injected transaction manager. When used on a `context` parameter as shown below, the context is no longer optional and you can always expect the transaction manager to be passed as a parameter.
|
||||
|
||||
To use these decorators, it’s recommended to include the following configurations in your `tsconfig.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
//other configurations
|
||||
"compilerOptions": {
|
||||
// other configurations
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementing list Method
|
||||
|
||||
The `list` method is used to retrieve a list of stock locations. It accepts the following parameters:
|
||||
|
||||
1. `selector`: This is the first parameter passed to the method, and the only required parameter. It is an object that has the following properties:
|
||||
1. `id`: an optional string or array of strings indicating the IDs of locations. It is used to filter the retrieved locations by ID.
|
||||
2. `name`: an optional string, array of strings, or a `StringComparisonOperator` object that is used to search and filter locations by their name. The `StringComparisonOperator` can have the following properties:
|
||||
1. `lt`: indicates a string that the name should be less than.
|
||||
2. `gt`: indicates a string that the name should be greater than.
|
||||
3. `gte`: indicates a string that the name should be greater than or equal to.
|
||||
4. `lte`: indicates a string the name should be less than or equal to.
|
||||
2. `config`: This is the second parameter and it is an optional parameter. It’s an object that can have any of the following optional properties:
|
||||
1. `select`: an array of strings indicating the attributes in your location entity to retrieve.
|
||||
2. `skip`: a number indicating how many location records to skip before retrieving the list.
|
||||
3. `take`: a number indicating how many location records to retrieve.
|
||||
4. `order`: an object indicating the order to retrieve the locations by. The order is specified per attribute. So, the attribute in your entity is the property of this object, and the value of the property is either `ASC` or `DESC`.
|
||||
3. `context`: This is the third parameter and it’s an optional parameter. This parameter should be used to inject the transaction manager, as explained in the [Method Decorators section](#using-method-decorators). It’s an object that can have any of the following optional properties:
|
||||
1. `transactionManager`: the transaction manager to use to perform database operations.
|
||||
|
||||
This method is expected to return an array of objects of the following type:
|
||||
|
||||
```ts
|
||||
type StockLocationDTO = {
|
||||
id: string;
|
||||
name: string;
|
||||
metadata: Record<string, unknown> | null;
|
||||
address_id: string;
|
||||
address?: StockLocationAddressDTO;
|
||||
created_at: string | Date;
|
||||
updated_at: string | Date;
|
||||
deleted_at: string | Date | null;
|
||||
}
|
||||
```
|
||||
|
||||
Here’s an example implementation of the method:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async list(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO[]> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO retrieve and return locations
|
||||
// for example
|
||||
return await locationRepo.find(selector)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example shows how you can use the context to retrieve the transaction manager, then retrieve your repository that you will use to retrieve and return locations. Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
### Implementing listAndCount Method
|
||||
|
||||
This method is similar to the list method, but it returns both the list of locations and a count of the locations.
|
||||
|
||||
It accepts the exact same parameters as the [list method](#implementing-list-method), but expects to return an array of two items. The first one being the list of locations, and the second one being the count of locations.
|
||||
|
||||
Here’s an example implementation of the method:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async listAndCount(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<[StockLocationDTO[], number]> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO retrieve and return locations
|
||||
// for example
|
||||
return await locationRepo.findAndCount(selector)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
### Implementing retrieve Method
|
||||
|
||||
This method is used to retrieve a single location. It accepts the following parameters:
|
||||
|
||||
1. `id`: this is the first parameter and is required. It’s a string indicating the ID of the location to retrieve.
|
||||
2. `config`: This is the second parameter and an optional parameter. It’s an object having the same properties as the `config` parameter mentioned in the [list method](#implementing-list-method).
|
||||
3. `context`: This is the third parameter and an optional parameter. It’s of the same type as the `context` parameter mentioned in the [list method](#implementing-list-method).
|
||||
|
||||
This method returns the location as an object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<StockLocationDTO> | undefined,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO retrieve the location using its ID
|
||||
// for example
|
||||
const [location] = await locationRepo.find({
|
||||
id,
|
||||
})
|
||||
return location
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
### Implementing create Method
|
||||
|
||||
This method is used to create a new location. It accepts the following parameters:
|
||||
|
||||
1. `input`: This is the first parameter, and it’s required. It's an object holding the following properties:
|
||||
1. `name`: this property is required, and its value is the name of the location.
|
||||
2. `address_id`: this property is optional, and it’s the ID of the address to associate with this location.
|
||||
3. `address`: this property is optional, and it’s an object holding address properties including `address_1`, `country_code`, `city`, and more.
|
||||
4. `metadata`: this property is optional, and it’s an object that should be stored in the `metadata` attribute of the created location.
|
||||
2. `context`: This is the second parameter and an optional parameter. It’s of the same type as the `context` parameter mentioned in the [list method](#implementing-list-method).
|
||||
|
||||
The method is expected to return the created location as an object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async create(
|
||||
input: CreateStockLocationInput,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO create the location and return it
|
||||
// for example
|
||||
return await locationRepo.create(input)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
### Implementing update Method
|
||||
|
||||
This method is used to update a location by its ID. It accepts the following parameters:
|
||||
|
||||
1. `id`: this is the first parameter and is required. It’s a string indicating the ID of the location to update.
|
||||
2. `input`: this is the second parameter and is required. It’s an object having any of the following optional properties:
|
||||
1. `name`: a string indicating the name of the location.
|
||||
2. `address_id`: the ID of the address to associate with this location.
|
||||
3. `address`: an object holding address properties including `address_1`, `country_code`, `city`, and more.
|
||||
4. `metadata`: an object that should be stored in the `metadata` attribute of the created location.
|
||||
3. `context`: This is the third parameter and an optional parameter. It’s of the same type as the `context` parameter mentioned in the [list method](#implementing-list-method).
|
||||
|
||||
This method is expected to return the updated location object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async update(
|
||||
id: string,
|
||||
input: UpdateStockLocationInput,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<StockLocationDTO> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO update the location and return it
|
||||
// for example
|
||||
await locationRepo.update(id, input)
|
||||
return await this.retrieve(id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
### Implementing delete Method
|
||||
|
||||
This method is used to delete a location by its ID. It accepts the following parameters:
|
||||
|
||||
1. `id`: this is the first parameter and is required. It’s a string indicating the ID of the location to delete.
|
||||
2. `context`: This is the second parameter and an optional parameter. It’s of the same type as the `context` parameter mentioned in the [list method](#implementing-list-method).
|
||||
|
||||
This method is not expected to return anything.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title=src/services/stock-location.ts
|
||||
class StockLocationService implements IStockLocationService {
|
||||
// ...
|
||||
@InjectEntityManager()
|
||||
async delete(
|
||||
id: string,
|
||||
@MedusaContext() context?: SharedContext | undefined
|
||||
): Promise<void> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(
|
||||
CustomStockLocation
|
||||
)
|
||||
|
||||
// TODO delete the location
|
||||
// for example
|
||||
await locationRepo.delete(id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `CustomStockLocation` with your stock location entity.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Use the Stock Location Service
|
||||
|
||||
After implementing your custom service along with any other necessary resources, you can test it out or use it in your Medusa backend. You can learn more about how to do that in the [Create Module documentation](../../../development/modules/create.mdx).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to create an inventory service](./create-inventory-service.md)
|
||||
- [Stock location module architecture](../stock-location-module.md)
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
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
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
### Step 4: Run Migration Script
|
||||
|
||||
After installing the Stock Location module, make sure to [run the migration script](#run-migration-script)
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
### Step 4: Run Migration Script
|
||||
|
||||
After installing both modules, make sure to [run the migration script](#run-migration-script)
|
||||
|
||||
---
|
||||
|
||||
## Run Migration Script
|
||||
|
||||
After installing both modules, run the following command to migrate current product variant information to fit the schema changes introduced by the modules:
|
||||
|
||||
```bash
|
||||
node ./node_modules/@medusajs/medusa/dist/scripts/migrate-inventory-items.js
|
||||
```
|
||||
|
||||
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,188 @@
|
||||
---
|
||||
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',
|
||||
}
|
||||
}} />
|
||||
|
||||
You can also create your own modules and use them with the Medusa backend.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/multiwarehouse/backend/create-inventory-service',
|
||||
label: 'Backend: Create Inventory Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an inventory service to be used in a custom inventory module.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/multiwarehouse/backend/create-stock-location-service',
|
||||
label: 'Backend: Create Stock Location Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a stock location service to be used in a custom stock location module.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## 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: '/modules/multiwarehouse/admin/manage-stock-locations',
|
||||
label: 'Admin: Manage Stock Locations',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage stock locations using admin APIs.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### 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: '/modules/multiwarehouse/admin/manage-inventory-items',
|
||||
label: 'Admin: Manage Inventory',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage inventory using the admin APIs.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### 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: '/modules/multiwarehouse/admin/manage-item-allocations-in-orders',
|
||||
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.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## 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 entity 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.
|
||||
@@ -0,0 +1,842 @@
|
||||
---
|
||||
description: 'Learn how to implement order-editing features for admins using the REST APIs. This guide includes how to create an order edit and move order edit to request state, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Edit an Order
|
||||
|
||||
In this document, you’ll learn how to create an order edit using the Admin API endpoints.
|
||||
|
||||
## Overview
|
||||
|
||||
The Admin API can be used to edit a customer’s order using the Order Editing feature.
|
||||
|
||||
The following changes can be made on an order:
|
||||
|
||||
- Add a new item to the original order
|
||||
- Edit an item’s quantity in the original order.
|
||||
- Delete an item from the original order.
|
||||
|
||||
Medusa then takes care of the calculation of subtotals, taxes, and more.
|
||||
|
||||
The Order Edit can then either be confirmed using the Storefront API as a customer, or force-confirmed using the Admin API as a merchant.
|
||||
|
||||
The changes are only reflected on the original order once the Order Edit is confirmed.
|
||||
|
||||

|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities related to Order Editing:
|
||||
|
||||
- Create an order edit.
|
||||
- Add, edit, and delete items from an order.
|
||||
- Revert an item change in an order edit.
|
||||
- Move order edit into request state.
|
||||
- Force-confirm an order edit.
|
||||
|
||||
:::note
|
||||
|
||||
You can perform other functionalities related to order editing. To learn more, check out the [API reference](https://docs.medusajs.com/api/admin#order-edits).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
You must have an existing order that you want to edit.
|
||||
|
||||
---
|
||||
|
||||
## Create an Order Edit
|
||||
|
||||
Before you can start making changes to an order, you have to create a new order edit.
|
||||
|
||||
To do that, send a request to the [Create an OrderEdit](https://docs.medusajs.com/api/admin#order-edits_postorderedits) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.create({
|
||||
order_id, // required
|
||||
})
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateOrderEdit } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const createOrderEdit = useAdminCreateOrderEdit()
|
||||
|
||||
const handleCreateOrderEdit = (orderId: string) => {
|
||||
createOrderEdit.mutate({
|
||||
order_id: orderId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/order-edits' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"order_id": "<ORDER_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint has one required request body parameter `order_id` which is the ID of the order this edit is being created for.
|
||||
|
||||
This request returns the Order Edit under the `order_edit` property.
|
||||
|
||||
:::info
|
||||
|
||||
You can only create one Order Edit at a time. So, if you try to create a new Order Edit for an order that is already being edited, you will receive an error.
|
||||
|
||||
:::info
|
||||
|
||||
---
|
||||
|
||||
## Make Changes to an Order’s Items
|
||||
|
||||
You can add new items into the original order, edit existing item in the original order, or delete items in the original order.
|
||||
|
||||
:::info
|
||||
|
||||
You can only make changes to items that have not been fulfilled yet in the order.
|
||||
|
||||
:::
|
||||
|
||||
### Add an Item
|
||||
|
||||
To add a new item to the original order, send a request to the [Add Line Item](https://docs.medusajs.com/api/admin#order-edits_postordereditseditlineitems) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.addLineItem(orderEditId, {
|
||||
quantity: 1,
|
||||
variant_id,
|
||||
})
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrderEditAddLineItem } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const addLineItem = useAdminOrderEditAddLineItem(orderEditId)
|
||||
|
||||
const handleAddLineItem =
|
||||
(quantity: number, variantId: string) => {
|
||||
addLineItem.mutate({
|
||||
quantity,
|
||||
variant_id: variantId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits/${orderEditId}/items`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 1,
|
||||
variant_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/items' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 1,
|
||||
"variant_id": "<VARIANT_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the order edit as a path parameter.
|
||||
|
||||
In the body of the request, you pass the two parameters `quantity` and `variant_id` which will be used to add a new item into the original order.
|
||||
|
||||
This request returns the Order Edit object. You can access returned item changes using `order_edit.changes`.
|
||||
|
||||
### Update an Item
|
||||
|
||||
You can edit an item’s quantity in the original order.
|
||||
|
||||
To update an item, send a request to the [Update Line Item](https://docs.medusajs.com/api/admin#order-edits_postordereditseditlineitemslineitem) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.updateLineItem(orderEditId, itemId, {
|
||||
quantity: 2,
|
||||
})
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrderEditUpdateLineItem } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const updateLineItem = useAdminOrderEditUpdateLineItem(
|
||||
orderEditId,
|
||||
itemId
|
||||
)
|
||||
|
||||
const handleUpdateLineItem = (quantity: number) => {
|
||||
updateLineItem.mutate({
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits/${orderEditId}/items/${itemId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 2,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 2
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the order edit and the ID of the item in the original order as path parameters.
|
||||
|
||||
In the body of the request, you can pass the `quantity` parameter, with its value being the new item quantity. In this example, you change the quantity of the item to `2`.
|
||||
|
||||
This request returns the Order Edit object. You can access returned item changes using `order_edit.changes`.
|
||||
|
||||
### Remove an Item
|
||||
|
||||
You can remove an item from the original order by sending a request to the [Remove Line Item](https://docs.medusajs.com/api/admin#order-edits_deleteordereditsordereditlineitemslineitem) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.removeLineItem(orderEditId, itemId)
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrderEditDeleteLineItem } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const removeLineItem = useAdminOrderEditDeleteLineItem(
|
||||
orderEditId,
|
||||
itemId
|
||||
)
|
||||
|
||||
const handleRemoveLineItem = () => {
|
||||
removeLineItem.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits/${orderEditId}/items/${itemId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the order edit’s ID and the ID of the item in the original order as path parameters.
|
||||
|
||||
This request returns the Order Edit object. You can access returned item changes using `order_edit.changes`.
|
||||
|
||||
---
|
||||
|
||||
## Revert an Item Change
|
||||
|
||||
A merchant might make a mistake while making a change to the original order’s items. Using the Admin API, you can revert item changes previously created.
|
||||
|
||||
To revert an item change, send a request to the [Delete Item Change](https://docs.medusajs.com/api/admin#order-edits_deleteordereditsorderedititemchange) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.deleteItemChange(orderEditId, changeId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteOrderEditItemChange } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const deleteItemChange = useAdminDeleteOrderEditItemChange(
|
||||
orderEditId,
|
||||
itemChangeId
|
||||
)
|
||||
|
||||
const handleDeleteItemChange = () => {
|
||||
deleteItemChange.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/order-edits/${orderEditId}/changes/${changeId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id, object, deleted)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/changes/<CHANGE_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the order edit’s ID and the item change’s ID as path parameters.
|
||||
|
||||
This request returns an object with the following properties:
|
||||
|
||||
- `id`: The ID of the deleted object. In this case, it’s the ID of the item change.
|
||||
- `object`: A string indicating the type of deleted object. In this case, it’s `item_change`.
|
||||
- `deleted`: A boolean value indicating whether the item change was deleted.
|
||||
|
||||
---
|
||||
|
||||
## Move into Request State
|
||||
|
||||
After an Order Edit is created and all the item changes are added, it must be moved into the request state.
|
||||
|
||||
To move an Order Edit into the request state, send a request to the [Request Confirmation](https://docs.medusajs.com/api/admin#order-edits_postordereditsordereditrequest) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.requestConfirmation(orderEditId)
|
||||
.then(({ order_edit }) => {
|
||||
console.log(
|
||||
order_edit.requested_at,
|
||||
order_edit.requested_by
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminRequestOrderEditConfirmation,
|
||||
} from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const requestOrderConfirmation =
|
||||
useAdminRequestOrderEditConfirmation(
|
||||
orderEditId
|
||||
)
|
||||
|
||||
const handleRequestConfirmation = () => {
|
||||
requestOrderConfirmation.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits/${orderEditId}/request`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.requested_at, order_edit.requested_by)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/request' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the order edit’s ID as a path parameter.
|
||||
|
||||
It returns the Order Edit object. You can access the following properties related to the confirmation request:
|
||||
|
||||
- `requested_at`: A timestamp indicating when the request confirmation was created.
|
||||
- `requested_by`: The ID of the user that requested this order edit.
|
||||
|
||||
:::tip
|
||||
|
||||
This request triggers the event `order-edit.requested`. You can use a subscriber to send an email to the customer with a link to view the order edit on the storefront. You can learn more in the [Events reference](../../../development/events/events-list.md).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Force-Confirm an Order Edit
|
||||
|
||||
There are two ways to confirm an Order Edit:
|
||||
|
||||
1. Using the Storefront API, which would confirm the Order Edit as a customer;
|
||||
2. Or using the Admin API, which would force-confirm the Order Edit as a merchant.
|
||||
|
||||
Once an Order Edit is confirmed, the changes to the items defined in the Order Edit will be reflected on the original order. So, a change in the totals of the original order, such as the subtotal, will be reflected as well.
|
||||
|
||||
This change can lead to either required additional payments from the customer, or a refund to be made to the customer.
|
||||
|
||||
When the merchant force-confirms the order edit, however, it bypasses the payment flow. This means that the admin can’t capture any additional payment.
|
||||
|
||||
This section covers how the Admin API can be used to force-confirm the Order Edit. You can refer to [this documentation to learn how to implement Order Edit confirmation on the storefront](../storefront/handle-order-edits.mdx).
|
||||
|
||||
To confirm an Order Edit, send a request to the [Confirm Order Edit](https://docs.medusajs.com/api/admin#order-edits_postordereditsordereditconfirm) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orderEdits.confirm(orderEditId)
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.confirmed_at, order_edit.confirmed_by)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminConfirmOrderEdit } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const confirmOrderEdit = useAdminConfirmOrderEdit(orderEditId)
|
||||
|
||||
const handleConfirmOrderEdit = () => {
|
||||
confirmOrderEdit.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/order-edits/${orderEditId}/confirm`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.confirmed_at, order_edit.confirmed_by)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/order-edits/<ORDER_EDIT_ID>/confirm' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the order edit ID as a path parameter.
|
||||
|
||||
It returns the Order Edit object. You can access the following properties related to the order edit confirmation:
|
||||
|
||||
- `confirmed_at`: A timestamp indicating when the order edit was confirmed.
|
||||
- `confirmed_by`: The ID of the user that confirmed the order edit.
|
||||
|
||||
---
|
||||
|
||||
## Receive Additional Payment
|
||||
|
||||
When the total after the order edit is greater than the total of the original order, the merchant can capture the difference from the customer.
|
||||
|
||||
For order edits that are confirmed by the customer, the customer authorizes the payment before confirming it. So, the merchant can capture the payment.
|
||||
|
||||
For force-confirmed order edits, as the customer didn’t authorize the payment, the merchant can’t capture the payment. It has to be handled manually by the merchant.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn how to allow customers to authorize payment on the storefront in [this documentation](../storefront/handle-order-edits.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### Capture Payment
|
||||
|
||||
If the payment is authorized by the customer, it can be captured by sending a request to the [Capture Payment](https://docs.medusajs.com/api/admin#payments_postpaymentspaymentcapture) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.payments.capturePayment(paymentId)
|
||||
.then(({ payment }) => {
|
||||
console.log(payment.captured_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminPaymentsCapturePayment } from "medusa-react"
|
||||
|
||||
const OrderEditPayment = () => {
|
||||
const capturePayment = useAdminPaymentsCapturePayment(
|
||||
paymentId
|
||||
)
|
||||
|
||||
const handleCapturePayment = () => {
|
||||
capturePayment.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEditPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/payments/${paymentId}/capture`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ payment }) => {
|
||||
console.log(payment.captured_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/payments/<PAYMENT_ID>/capture' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the payment as a path parameter. The payment can be retrieved from the order by accessing the array property `order.payments`.
|
||||
|
||||
It returns in the response the full Payment object.
|
||||
|
||||
## Refund Payment
|
||||
|
||||
When the total after the order edit is less than the original order total, the merchant can refund the difference to the customer.
|
||||
|
||||
To refund the difference to the customer, send a request to the [Refund Payment](https://docs.medusajs.com/api/admin#payments_postpaymentspaymentrefunds) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
import { RefundReason } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
medusa.admin.payments.refundPayment(paymentId, {
|
||||
amount,
|
||||
reason: RefundReason.DISCOUNT, // for example
|
||||
})
|
||||
.then(({ refund }) => {
|
||||
console.log(refund.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminPaymentsRefundPayment } from "medusa-react"
|
||||
import { RefundReason } from "@medusajs/medusa"
|
||||
|
||||
const OrderEditPayment = () => {
|
||||
const refundPayment = useAdminPaymentsRefundPayment(paymentId)
|
||||
|
||||
const handleRefundPayment =
|
||||
(amount: number, reason: RefundReason) => {
|
||||
refundPayment.mutate({
|
||||
amount,
|
||||
reason,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEditPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/payments/${paymentId}/refund`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount,
|
||||
reason: "discount",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ refund }) => {
|
||||
console.log(refund.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/payments/<PAYMENT_ID>/refund' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"amount": 1000,
|
||||
"reason": "discount"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the payment as a path parameter. The payment can be retrieved from the order by accessing the array property `order.payments`.
|
||||
|
||||
In the request’s body parameters, the `amount` field parameter is required. It is the amount to be refunded.
|
||||
|
||||
The `reason` request body parameter is also required. Its value is a string that can be one of the following:
|
||||
|
||||
- `discount`
|
||||
- `return`
|
||||
- `swap`
|
||||
- `claim`
|
||||
- `other`
|
||||
|
||||
:::note
|
||||
|
||||
Check out what other parameters can be sent in the [API reference](https://docs.medusajs.com/api/admin#payments_postpaymentspaymentrefunds).
|
||||
|
||||
:::
|
||||
|
||||
It returns in the response the full Refund object.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Handle order edits on the storefront](../storefront/handle-order-edits.mdx)
|
||||
@@ -0,0 +1,663 @@
|
||||
---
|
||||
description: "Learn how to manage claims using the admin REST APIs. This guide includes how to view an order's claims, "
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Claims
|
||||
|
||||
In this document, you’ll learn how to manage claims using the admin REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using Medusa’s claim admin REST APIs, you can manage claims and perform related admin functionalities.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- View an order’s claims
|
||||
- Manage claims, including creating, updating, and canceling claims.
|
||||
- Manage a claim’s fulfillment, including creating a fulfillment, creating a shipment, and canceling a fulfillment.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn about managing returns part of a claim in the [Manage Returns documentation](./manage-returns.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## View Order’s Claims
|
||||
|
||||
To view an order’s claims, you can retrieve the order using the [Get Order endpoint](https://docs.medusajs.com/api/admin#orders_getordersorder) and access the order’s claims:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.retrieve(orderId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrder } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useAdminOrder(orderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && (
|
||||
<>
|
||||
<span>{order.display_id}</span>
|
||||
{order.claims?.length > 0 && (
|
||||
<ul>
|
||||
{order.claims.map((claim) => (
|
||||
<li key={claim.id}>{claim.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/orders/<ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the order’s ID as a path parameter.
|
||||
|
||||
The request returns the order as an object. In that object, you can access an array of claim objects using the property `claims` of the order object.
|
||||
|
||||
---
|
||||
|
||||
## Create Claim
|
||||
|
||||
You can create a claim by sending a request to the [Create Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaims):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.createClaim(orderId, {
|
||||
type: "refund",
|
||||
claim_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateClaim } from "medusa-react"
|
||||
|
||||
const CreateClaim = () => {
|
||||
const createClaim = useAdminCreateClaim(orderId)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createClaim.mutate({
|
||||
type: "refund",
|
||||
claim_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateClaim
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/claims`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "refund",
|
||||
claim_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"type": "refund",
|
||||
"claim_items": [
|
||||
{
|
||||
"item_id": "<ITEM_ID>",
|
||||
"quantity": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order ID to be passed as a path parameter.
|
||||
|
||||
In the request body, the following parameters are required:
|
||||
|
||||
- `type`: a string indicating the type of claim to be created. Its value can either be `replace` or `refund`. If the type is `replace`, you can pass the `additional_items` parameter with an array of new items to send to the customer. If the type is `refund`, you can pass the `refund_amount` parameter if you want to specify a custom refund amount.
|
||||
- `claim_items`: an array of objects, each object being the item in the order that the claim is being created for. In the object, you must pass the following properties:
|
||||
- `item_id`: a string indicating the ID of the line item in the order.
|
||||
- `quantity`: a number indicating the quantity of the claim.
|
||||
|
||||
There are other optional parameters that you can pass. You can also pass a return reason for each of the claim items. You can learn about the optional request body parameters in the [API reference](https://docs.medusajs.com/api/admin#orders_postordersorderclaims).
|
||||
|
||||
:::note
|
||||
|
||||
Learn how to manage return reasons in [this documentation](./manage-returns.mdx#manage-return-reasons).
|
||||
|
||||
:::
|
||||
|
||||
The request returns the updated order as an object. You can access the order’s claims using the `claims` property of the order object. The value of the `claims` property is an array of claim objects.
|
||||
|
||||
---
|
||||
|
||||
## Update a Claim
|
||||
|
||||
You can update a claim by sending a request to the [Update Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaim):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.updateClaim(orderId, claimId, {
|
||||
no_notification: true,
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateClaim } from "medusa-react"
|
||||
|
||||
const UpdateClaim = () => {
|
||||
const updateClaim = useAdminUpdateClaim(orderId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateClaim.mutate({
|
||||
claim_id,
|
||||
no_notification: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateClaim
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/orders/${orderId}/claims/${claimId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
no_notification: true,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims/<CLAIM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"no_notification": true
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the order and the claim to be passed as path parameters.
|
||||
|
||||
In the request body, you can pass any of the claim’s fields that you want to update as parameters. In the example above, the `no_notification` field is updated.
|
||||
|
||||
The request returns the updated order as an object. You can access the order’s claims using the `claims` property of the order object. The value of the `claims` property is an array of claim objects.
|
||||
|
||||
---
|
||||
|
||||
## Manage a Claim’s Fulfillments
|
||||
|
||||
### View Claim’s Fulfillments
|
||||
|
||||
Fulfillments are available on a claim object under the `fulfillments` property, which is an array of fulfillment objects.
|
||||
|
||||
### Create Fulfillment
|
||||
|
||||
You can create a fulfillment for a claim by sending a request to the [Create Claim Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimfulfillments):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.fulfillClaim(orderId, claimId, {
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminFulfillClaim } from "medusa-react"
|
||||
|
||||
const FulfillClaim = () => {
|
||||
const fulfillClaim = useAdminFulfillClaim(orderId)
|
||||
// ...
|
||||
|
||||
const handleFulfill = () => {
|
||||
fulfillClaim.mutate({
|
||||
claim_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default FulfillClaim
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/claims/${claimId}/fulfillments`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims/<CLAIM_ID>/fulfillments' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order and claim IDs as path parameters.
|
||||
|
||||
In the request body, you can pass optional parameters such as `metadata` or `no_notification`. These parameters will be used to create the fulfillment. You can learn more about available request body parameters in the [API reference](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimfulfillments).
|
||||
|
||||
The request returns the updated order as an object. You can access the order’s claims using the `claims` property of the order object. The value of the `claims` property is an array of claim objects.
|
||||
|
||||
### Create a Shipment
|
||||
|
||||
You can create a shipment for a claim by sending a request to the [Create Claim Shipment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimshipments):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.createClaimShipment(orderId, claimId, {
|
||||
fulfillment_id,
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateClaimShipment } from "medusa-react"
|
||||
|
||||
const CreateShipment = () => {
|
||||
const createShipment = useAdminCreateClaimShipment(orderId)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createShipment.mutate({
|
||||
claim_id,
|
||||
fulfillment_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateShipment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/claims/${claimId}/shipments`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fulfillment_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims/<CLAIM_ID>/shipments' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"fulfillment_id": "<FUL_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order and claim IDs as path parameters.
|
||||
|
||||
In the request body, it’s required to pass the `fulfillment_id` parameter, which is the ID of the fulfillment the shipment is being created for. You can pass other optional parameters, such as an array of tracking numbers. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimshipments).
|
||||
|
||||
The request returns the updated order as an object. As mentioned before, a claim’s fulfillments can be accessed using the `fulfillments` property of a claim object. You can access the shipments, known as tracking links, of a fulfillment using the `tracking_links` property of a fulfillment object. The value of `tracking_links` is an array of tracking link objects.
|
||||
|
||||
You can alternatively access the tracking numbers using the `tracking_numbers` property of a fulfillment object, which is an array of strings.
|
||||
|
||||
You can access the status of a claim’s fulfillment using the `fulfillment_status` property of a claim object.
|
||||
|
||||
### Cancel Fulfillment
|
||||
|
||||
:::note
|
||||
|
||||
You can’t cancel a fulfillment that has a shipment
|
||||
|
||||
:::
|
||||
|
||||
You can cancel a fulfillment by sending a request to the [Cancel Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersclaimfulfillmentscancel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.cancelClaimFulfillment(
|
||||
orderId,
|
||||
claimId,
|
||||
fulfillmentId
|
||||
)
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCancelClaimFulfillment } from "medusa-react"
|
||||
|
||||
const CancelFulfillment = () => {
|
||||
const cancelFulfillment = useAdminCancelClaimFulfillment(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCancel = () => {
|
||||
cancelFulfillment.mutate({
|
||||
claim_id,
|
||||
fulfillment_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CancelFulfillment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/claims/${claimId}/fulfillments/${fulfillmentId}/cancel`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims/<CLAIM_ID>/fulfillments/<FUL_ID>/cancel' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order, claim, and fulfillment IDs to be passed as path parameters.
|
||||
|
||||
The request returns the updated order as an object. You can access the claims using the `claims` property of the order object, which is an array of claim objects.
|
||||
|
||||
You can check the fulfillment status of a claim using the `fulfillment_status` property of the claim object.
|
||||
|
||||
---
|
||||
|
||||
## Cancel Claim
|
||||
|
||||
:::note
|
||||
|
||||
You can’t cancel a claim that has been refunded. You must also cancel the claim’s fulfillments and return first.
|
||||
|
||||
:::
|
||||
|
||||
You can cancel a claim by sending a request to the [Cancel Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersclaimcancel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.cancelClaim(orderId, claimId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCancelClaim } from "medusa-react"
|
||||
|
||||
const CancelClaim = () => {
|
||||
const cancelClaim = useAdminCancelClaim(orderId)
|
||||
// ...
|
||||
|
||||
const handleCancel = () => {
|
||||
cancelClaim.mutate(claimId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CancelClaim
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/claims/${claimId}/cancel`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.claims)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/claims/<CLAIM_ID>/cancel' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order and claim IDs as path parameters.
|
||||
|
||||
The request returns the updated order as an object. You can access the claims using the `claims` property of the order object, which is an array of claim objects.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage returns](./manage-returns.mdx)
|
||||
- [How to manage orders](./manage-orders.mdx)
|
||||
@@ -0,0 +1,838 @@
|
||||
---
|
||||
description: 'Learn how to manage draft orders using the admin REST APIs. This guide includes how to manage draft orders including listing and creating them, manage their line items, and register their payment.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Draft Orders
|
||||
|
||||
In this document, you’ll learn how to manage draft orders using the admin REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the draft orders admin REST APIs, you can manage draft orders and perform other related functionalities.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage draft orders, including listing, creating, updating, and deleting draft orders.
|
||||
- Managing line items in draft orders, including adding, updating, and deleting items from a draft order.
|
||||
- Authorize and capture a draft order’s payment.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Draft Orders
|
||||
|
||||
You can list draft orders by sending a request to the [List Draft Orders endpoint](https://docs.medusajs.com/api/admin#draft-orders_getdraftorders):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.list()
|
||||
.then(({ draft_orders, limit, offset, count }) => {
|
||||
console.log(draft_orders.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrders } from "medusa-react"
|
||||
|
||||
const DraftOrders = () => {
|
||||
const { draft_orders, isLoading } = useAdminDraftOrders()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{draft_orders && !draft_orders.length && (
|
||||
<span>No Draft Orders</span>
|
||||
)}
|
||||
{draft_orders && draft_orders.length > 0 && (
|
||||
<ul>
|
||||
{draft_orders.map((order) => (
|
||||
<li key={order.id}>{order.display_id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DraftOrders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_orders, limit, offset, count }) => {
|
||||
console.log(draft_orders.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/draft-orders' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any path or query parameters. You can pass search or pagination query parameters as explained in the [API reference](https://docs.medusajs.com/api/admin#draft-orders_getdraftorders).
|
||||
|
||||
The request returns an array of draft order objects along with [pagination parameters](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
---
|
||||
|
||||
## Create a Draft Order
|
||||
|
||||
You can create a draft order by sending a request to the [Create Draft Order endpoint](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.create({
|
||||
email,
|
||||
region_id,
|
||||
items: [
|
||||
{
|
||||
// defined product
|
||||
quantity: 1,
|
||||
variant_id,
|
||||
},
|
||||
{
|
||||
// custom product
|
||||
quantity: 1,
|
||||
unit_price: 1000,
|
||||
title: "Custom Product",
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id,
|
||||
// for custom shipping price
|
||||
price,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateDraftOrder } from "medusa-react"
|
||||
|
||||
const CreateDraftOrder = () => {
|
||||
const createDraftOrder = useAdminCreateDraftOrder()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createDraftOrder.mutate({
|
||||
email,
|
||||
region_id,
|
||||
items: [
|
||||
{
|
||||
// defined product
|
||||
quantity: 1,
|
||||
variant_id,
|
||||
},
|
||||
{
|
||||
// custom product
|
||||
quantity: 1,
|
||||
unit_price: 1000,
|
||||
title: "Custom Product",
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id,
|
||||
// for custom shipping price
|
||||
price,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateDraftOrder
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
region_id,
|
||||
items: [
|
||||
{
|
||||
// defined product
|
||||
quantity: 1,
|
||||
variant_id,
|
||||
},
|
||||
{
|
||||
// custom product
|
||||
quantity: 1,
|
||||
unit_price: 1000,
|
||||
title: "Custom Product",
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id,
|
||||
// for custom shipping price
|
||||
price,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/draft-orders' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "<EMAIL>",
|
||||
"region_id": "<REGION_ID>",
|
||||
"items": [
|
||||
{
|
||||
"quantity": 1,
|
||||
"variant_id": "<VARIANT_ID>"
|
||||
},
|
||||
{
|
||||
"quantity": 1,
|
||||
"unit_price": 1000,
|
||||
"title": "Custom Product"
|
||||
}
|
||||
],
|
||||
"shipping_methods": [
|
||||
{
|
||||
"option_id": "<OPTION_ID>",
|
||||
"price": 1000
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `email`: a string indicating the email of the customer to associate this draft order with.
|
||||
- `region_id`: a string indicating the ID of the region this draft order belongs to.
|
||||
- `shipping_methods`: an array of objects, each object being a shipping method to use for the draft order. The object is required to have the `option_id` property which is the ID of the shipping option to use. You can optionally specify a `price` property to set a custom amount for the price.
|
||||
|
||||
You can also pass other optional parameters. For example, the `items` array can be passed to add line items to the draft order. There are two types of items that you can add:
|
||||
|
||||
1. Defined product variants. This is done by passing the `variant_id` property, with its value being the ID of the product variant to add.
|
||||
2. Custom products that aren’t defined in your store. This is done by passing the `unit_price` property with its value being the price of the custom product, and the `title` property with its value being the title of the custom product.
|
||||
|
||||
You must also pass for each item its quantity.
|
||||
|
||||
To learn about other optional request body parameters, such as passing shipping or billing addresses, check out the [API reference](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders).
|
||||
|
||||
The request returns the created draft order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Draft Order
|
||||
|
||||
You can retrieve a draft order by sending a request to the [Get Draft Order endpoint](https://docs.medusajs.com/api/admin#draft-orders_getdraftordersdraftorder):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.retrieve(draftOrderId)
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrder } from "medusa-react"
|
||||
|
||||
const DraftOrder = () => {
|
||||
const {
|
||||
draft_order,
|
||||
isLoading,
|
||||
} = useAdminDraftOrder(draftOrderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{draft_order && <span>{draft_order.display_id}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DraftOrder
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order’s ID as a path parameter.
|
||||
|
||||
The request returns the draft order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Update Draft Order
|
||||
|
||||
You can update a draft order by sending a request to the [Update Draft Order endpoint](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorder):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.update(draftOrderId, {
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateDraftOrder } from "medusa-react"
|
||||
|
||||
const UpdateDraftOrder = () => {
|
||||
const updateDraftOrder = useAdminUpdateDraftOrder(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateDraftOrder.mutate({
|
||||
email: "user@example.com",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateDraftOrder
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order’s ID as a path parameter.
|
||||
|
||||
In the request body, you can pass any of the draft order’s fields to update. In the example above, you update the `email` field. Check out the [API reference](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorder) to learn about other accepted request body parameters.
|
||||
|
||||
The request returns the updated draft order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Manage Line Items
|
||||
|
||||
### Add Line Items
|
||||
|
||||
You can add line items to a draft order by sending a request to the [Create Line Items endpoint](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderlineitems):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.addLineItem(draftOrderId, {
|
||||
quantity: 1,
|
||||
})
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrderAddLineItem } from "medusa-react"
|
||||
|
||||
const AddLineItem = () => {
|
||||
const addLineItem = useAdminDraftOrderAddLineItem(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAdd = () => {
|
||||
addLineItem.mutate({
|
||||
quantity: 1,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default AddLineItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}/line-items`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 1,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>/line-items' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 1
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the draft order as a path parameter.
|
||||
|
||||
In the request body, it’s required to pass the `quantity` parameter, which is a number indicating the quantity of the item in the draft order. You can also pass other optional parameters based on the type of line item you’re adding.
|
||||
|
||||
There are two types of items that you can add:
|
||||
|
||||
1. Defined product variants. This is done by passing the `variant_id` parameter, with its value being the ID of the product variant to add.
|
||||
2. Custom products that aren’t defined in your store. This is done by passing the `unit_price` parameter with its value being the price of the custom product, and the `title` property with its value being the title of the custom product.
|
||||
|
||||
Check the [API reference](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderlineitems) for a full list of request body parameters.
|
||||
|
||||
The request returns the updated draft order as an object. You can access the draft order’s items by accessing the `carts` property in the draft item, then the `items` property. The `items` property is an array of line item objects.
|
||||
|
||||
### Update Line Item
|
||||
|
||||
You can update a line item by sending a request to the [Update Line Item endpoint](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderlineitemsitem):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.updateLineItem(draftOrderId, itemId, {
|
||||
quantity: 1,
|
||||
})
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrderUpdateLineItem } from "medusa-react"
|
||||
|
||||
const UpdateLineItem = () => {
|
||||
const updateLineItem = useAdminDraftOrderUpdateLineItem(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateLineItem.mutate({
|
||||
item_id,
|
||||
quantity: 1,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateLineItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}/line-items/${itemId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 1,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>/line-items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"quantity": 1
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order and line item’s IDs as path parameters.
|
||||
|
||||
In the request body, you can pass any of the line item’s fields as parameters to update them. In the example above, you pass the `quantity` parameter to update the quantity of the item. Check out the [API reference](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderlineitemsitem) for a full list of request body parameters.
|
||||
|
||||
The request returns the updated draft order as an object. You can access the draft order’s items by accessing the `carts` property in the draft item, then the `items` property. The `items` property is an array of line item objects.
|
||||
|
||||
### Delete Line Item
|
||||
|
||||
You can delete a line item by sending a request to the [Delete Line Item endpoint](https://docs.medusajs.com/api/admin#draft-orders_deletedraftordersdraftorderlineitemsitem):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.removeLineItem(draftOrderId, itemId)
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrderRemoveLineItem } from "medusa-react"
|
||||
|
||||
const DeleteLineItem = () => {
|
||||
const deleteLineItem = useAdminDraftOrderRemoveLineItem(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteLineItem.mutate(itemId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteLineItem
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}/line-items/${itemId}`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ draft_order }) => {
|
||||
console.log(draft_order.cart.items)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>/line-items/<ITEM_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order and line item’s IDs as path parameters.
|
||||
|
||||
The request returns the updated draft order as an object. You can access the draft order’s items by accessing the `carts` property in the draft item, then the `items` property. The `items` property is an array of line item objects.
|
||||
|
||||
---
|
||||
|
||||
## Register Draft Order Payment
|
||||
|
||||
Registering the draft order’s payment leads to authorizing and capturing the payment using the `system` payment method. This would complete the draft order and create an order from it.
|
||||
|
||||
You can register the draft order payment by sending a request to the [Register Draft Order Payment endpoint](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderregisterpayment):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.markPaid(draftOrderId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDraftOrderRegisterPayment } from "medusa-react"
|
||||
|
||||
const RegisterPayment = () => {
|
||||
const registerPayment = useAdminDraftOrderRegisterPayment(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handlePayment = () => {
|
||||
registerPayment.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default RegisterPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}/pay`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/draft-orders/<DRAFT_ORDER_ID>/pay' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order’s ID as a path parameter.
|
||||
|
||||
The request returns the order created from the draft order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Draft Order
|
||||
|
||||
You can delete a draft order by sending a request to the [Delete Draft Order endpoint](https://docs.medusajs.com/api/admin#draft-orders_deletedraftordersdraftorder):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.draftOrders.delete(draftOrderId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteDraftOrder } from "medusa-react"
|
||||
|
||||
const DeleteDraftOrder = () => {
|
||||
const deleteDraftOrder = useAdminDeleteDraftOrder(
|
||||
draftOrderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteDraftOrder.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteDraftOrder
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/draft-orders/${draftOrderId}`, {
|
||||
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/draft-orders/<DRAFT_ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the draft order’s ID as a path parameter.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted draft order.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `draft-order`.
|
||||
- `deleted`: A boolean value indicating whether the draft order was deleted.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage orders](./manage-orders.mdx)
|
||||
- [Orders architecture overview](../orders.md)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,652 @@
|
||||
---
|
||||
description: "Learn how to manage returns using the admin REST APIs. This guide includes how to manage return reasons, view returns, mark a return as received, and cancel a return."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Returns
|
||||
|
||||
In this document, you’ll learn how to manage returns using the admin REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the returns, orders, and return reasons admin REST APIs, you can manage returns and perform other related functionalities.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage Return Reasons. This includes listing return reasons, creating, updating, and deleting them.
|
||||
- View an Order’s, Swap’s, and Claim’s Returns
|
||||
- Mark a Return Received
|
||||
- Cancel a Return
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Manage Return Reasons
|
||||
|
||||
Return reasons allow you to specify why an item is returned. They are especially useful for claims, but you can also use them to ask customers to specify the reason they’re requesting to return an item.
|
||||
|
||||
### List Return Reasons
|
||||
|
||||
You can list available return reasons using the [List Return Reasons endpoint](https://docs.medusajs.com/api/admin#return-reasons_getreturnreasons):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returnReasons.list()
|
||||
.then(({ return_reasons }) => {
|
||||
console.log(return_reasons.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminReturnReasons } from "medusa-react"
|
||||
|
||||
const ReturnReasons = () => {
|
||||
const { return_reasons, isLoading } = useAdminReturnReasons()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{return_reasons && !return_reasons.length && (
|
||||
<span>No Return Reasons</span>
|
||||
)}
|
||||
{return_reasons && return_reasons.length > 0 && (
|
||||
<ul>
|
||||
{return_reasons.map((reason) => (
|
||||
<li key={reason.id}>
|
||||
{reason.label}: {reason.value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReturnReasons
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/return-reasons`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ return_reasons }) => {
|
||||
console.log(return_reasons.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/return-reasons' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require or accept any path or query parameters.
|
||||
|
||||
The request returns an array of return reason objects.
|
||||
|
||||
### Create a Return Reason
|
||||
|
||||
You can create a return reason using the [Create Return Reason endpoint](https://docs.medusajs.com/api/admin#return-reasons_postreturnreasons):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returnReasons.create({
|
||||
label: "Damaged",
|
||||
value: "damaged",
|
||||
})
|
||||
.then(({ return_reason }) => {
|
||||
console.log(return_reason.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateReturnReason } from "medusa-react"
|
||||
|
||||
const CreateReturnReason = () => {
|
||||
const createReturnReason = useAdminCreateReturnReason()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createReturnReason.mutate({
|
||||
label: "Damaged",
|
||||
value: "damaged",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateReturnReason
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/return-reasons`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
label: "Damaged",
|
||||
value: "damaged",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ return_reason }) => {
|
||||
console.log(return_reason.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/return-reasons' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"label": "Damaged",
|
||||
"value": "damaged"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `label`: a string that will be used as the label of the return reason. This is typically the label shown to the customer or the merchant.
|
||||
- `value`: a string that is unique among return reasons. It is typically used internally for identification
|
||||
|
||||
You can also pass other optional request body parameters, such as a `description` parameter or a `parent_return_reason_id` if this return reason is a child of another. You can learn more about available optional request body parameters in the [API reference](https://docs.medusajs.com/api/admin#return-reasons_postreturnreasons).
|
||||
|
||||
This request returns the created return reason as an object.
|
||||
|
||||
### Update a Return Reason
|
||||
|
||||
You can update a return reason by sending a request to the [Update Return Reason endpoint](https://docs.medusajs.com/api/admin#return-reasons_postreturnreasonsreason):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returnReasons.update(returnReasonId, {
|
||||
label: "Damaged",
|
||||
})
|
||||
.then(({ return_reason }) => {
|
||||
console.log(return_reason.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateReturnReason } from "medusa-react"
|
||||
|
||||
const UpdateReturnReason = () => {
|
||||
const updateReturnReason = useAdminUpdateReturnReason(
|
||||
returnReasonId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateReturnReason.mutate({
|
||||
label: "Damaged",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateReturnReason
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/return-reasons/${returnReasonId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
label: "Damaged",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ return_reason }) => {
|
||||
console.log(return_reason.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/return-reasons/<REASON_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"label": "Damaged"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the return reason ID to be passed as a path parameter.
|
||||
|
||||
In the request body, you can pass any of the return reason’s fields that you want to update as parameters. In the example above, the label is passed in the request body to be updated.
|
||||
|
||||
The request returns the updated return reason as an object.
|
||||
|
||||
### Delete a Return Reason
|
||||
|
||||
You can delete a return reason by sending a request to the [Delete Return Reason endpoint](https://docs.medusajs.com/api/admin#return-reasons_deletereturnreason):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returnReasons.delete(returnReasonId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteReturnReason } from "medusa-react"
|
||||
|
||||
const DeleteReturnReason = () => {
|
||||
const deleteReturnReason = useAdminDeleteReturnReason(
|
||||
returnReasonId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteReturnReason.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteReturnReason
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/return-reasons/${returnReasonId}`, {
|
||||
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/return-reasons/<REASON_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the return reason ID as a path parameter.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted return reason.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `return_reason`.
|
||||
- `deleted`: A boolean value indicating whether the return reason was deleted.
|
||||
|
||||
---
|
||||
|
||||
## View an Order’s Returns
|
||||
|
||||
:::note
|
||||
|
||||
You can view all returns in your commerce system, regardless of which order they belong to, using the [List Returns endpoint](https://docs.medusajs.com/api/admin#return-reasons_getreturnreasons).
|
||||
|
||||
:::
|
||||
|
||||
When you retrieve an order using the [Get Order endpoint](https://docs.medusajs.com/api/admin#orders_getordersorder), you can access the returns within the order object:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.retrieve(orderId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.returns)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrder } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useAdminOrder(orderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && (
|
||||
<>
|
||||
<span>{order.display_id}</span>
|
||||
{order.returns?.length > 0 && (
|
||||
<ul>
|
||||
{order.returns.map((orderReturn) => (
|
||||
<li key={orderReturn.id}>{orderReturn.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.returns)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/orders/<ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order ID as a path parameter.
|
||||
|
||||
The request returns the order as an object. In that object, you can access an array of return objects using the property `returns` of the order object.
|
||||
|
||||
---
|
||||
|
||||
## View a Swap’s Return
|
||||
|
||||
You can retrieve a swap either using the Get Order endpoint as explained [here](./manage-swaps.mdx#view-orders-swaps), or using the [List Swaps](https://docs.medusajs.com/api/admin#swaps_getswaps) or [Get Swap endpoint](https://docs.medusajs.com/api/admin#swaps_getswapsswap).
|
||||
|
||||
In a swap object, the swap’s return is available under the `return_order` property.
|
||||
|
||||
---
|
||||
|
||||
## View a Claim’s Return
|
||||
|
||||
You can retrieve a claim using the Get Order endpoint as explained [here](./manage-claims#view-orders-claims). In a claim object, the claim’s return is available under the `return_order` property.
|
||||
|
||||
---
|
||||
|
||||
## Mark a Return as Received
|
||||
|
||||
You can mark a return as received by sending a request to the [Receive a Return endpoint](https://docs.medusajs.com/api/admin#returns_postreturnsreturnreceive):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returns.receive(return_id, {
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data.return.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminReceiveReturn } from "medusa-react"
|
||||
|
||||
const ReceiveReturn = () => {
|
||||
const receiveReturn = useAdminReceiveReturn(
|
||||
returnId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleReceive = () => {
|
||||
receiveReturn.mutate({
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ReceiveReturn
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/returns/${returnId}/receive`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data.return.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/returns/<RETURN_ID>/receive' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"items": [
|
||||
{
|
||||
"item_id": "<ITEM_ID>",
|
||||
"quantity": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the return as a path parameter.
|
||||
|
||||
In the request body, the `items` parameter is required. It’s an array of objects having the following properties:
|
||||
|
||||
- `item_id`: a string indicating the ID of the line item in the order that is received.
|
||||
- `quantity`: a number indicating the quantity of the line item that was received.
|
||||
|
||||
By default, the refund amount specified in the return will be refunded. If you want to refund a custom amount, you can pass the optional request body parameter `refund`, with its value being a number indicating the amount to refund. The amount must be less than the order’s `refundable_amount`.
|
||||
|
||||
The request returns the updated return as an object.
|
||||
|
||||
---
|
||||
|
||||
## Cancel a Return
|
||||
|
||||
:::note
|
||||
|
||||
A received return can’t be canceled.
|
||||
|
||||
:::
|
||||
|
||||
You can cancel a return by sending a request to the [Cancel Return endpoint](https://docs.medusajs.com/api/admin#returns_postreturnsreturncancel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.returns.cancel(returnId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.returns)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCancelReturn } from "medusa-react"
|
||||
|
||||
const CancelReturn = () => {
|
||||
const cancelReturn = useAdminCancelReturn(
|
||||
returnId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCancel = () => {
|
||||
cancelReturn.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CancelReturn
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/returns/${returnId}/cancel`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.returns)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/returns/<RETURN_ID>/cancel' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the return ID to be passed as a path parameter.
|
||||
|
||||
The request returns one of the following object types:
|
||||
|
||||
- If a return belongs to a swap, a swap object is returned. You can access the returns in the `return_order` property of the swap object.
|
||||
- If a return belongs to a claim, a claim object is returned. You can access the returns in the `return_order` property of the claim object.
|
||||
- Otherwise, an order object is returned. You can access the returns in the `returns` property of the order object.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage orders using the REST APIs](./manage-orders.mdx)
|
||||
- [How to manage swaps using the REST APIs](./manage-swaps.mdx)
|
||||
- [How to manage claims using the REST APIs](./manage-claims.mdx)
|
||||
@@ -0,0 +1,543 @@
|
||||
---
|
||||
description: "Learn how to manage swaps using the admin REST APIs. This guide includes how to view an order's swaps, manage a swap's payment and fulfillment, and cancel a swap."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Swaps
|
||||
|
||||
In this document, you’ll learn how to manage swaps using the admin REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the order’s admin REST APIs, you can manage and process the swaps of an order in your commerce store.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- View an order’s swaps.
|
||||
- Process a swap’s payment.
|
||||
- Manage a swap’s fulfillment. This includes creating a fulfillment, creating a shipment, and canceling a fulfillment.
|
||||
- Cancel a swap.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn about managing returns in the [Manage Returns documentation](./manage-returns.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## View Order’s Swaps
|
||||
|
||||
:::note
|
||||
|
||||
If you want to view all swaps in your system, and not swaps specific to an order, you can use the [List Swaps endpoint](https://docs.medusajs.com/api/admin#swaps_getswaps) instead.
|
||||
|
||||
:::
|
||||
|
||||
You can view an order’s swaps by retrieving the order using the [Get Order endpoint](https://docs.medusajs.com/api/admin#orders_getordersorder):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.retrieve(orderId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminOrder } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useAdminOrder(orderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && (
|
||||
<>
|
||||
<span>{order.display_id}</span>
|
||||
{order.swaps?.length > 0 && (
|
||||
<ul>
|
||||
{order.swaps.map((swap) => (
|
||||
<li key={swap.id}>{swap.difference_due}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/orders/<ORDER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order ID to be passed as a path parameter.
|
||||
|
||||
The request returns the order as an object. The order’s swaps are available under the `swaps` property, which is an array of swap objects.
|
||||
|
||||
---
|
||||
|
||||
## Process Swap Payment
|
||||
|
||||
Processing a swap’s payment can mean either refunding or capturing payment, depending on the value of `difference_due` of the swap. If the `difference_due` is less than 0, then the payment should be refunded. If it’s greater than 0, then the payment should be captured. If it’s 0, no payment processing is performed.
|
||||
|
||||
Regardless of whether you need to refund or capture the payment, you can process the swap’s payment by sending a request to the [Process Swap Payment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapprocesspayment):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.processSwapPayment(orderId, swapId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminProcessSwapPayment } from "medusa-react"
|
||||
|
||||
const ProcessPayment = () => {
|
||||
const processPayment = useAdminProcessSwapPayment(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleProcess = () => {
|
||||
processPayment.mutate(swapId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ProcessPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/swaps/${swapId}/process-payment`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/swaps/<SWAP_ID>/process-payment' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order ID and the swap ID as path parameters.
|
||||
|
||||
The request returns the updated order as an object. You can access the swaps of the order in the `order.swaps` property. It’s an array of swap objects.
|
||||
|
||||
---
|
||||
|
||||
## Manage a Swap’s Fulfillments
|
||||
|
||||
### View Swap’s Fulfillments
|
||||
|
||||
Fulfillments are available on a swap object under the `fulfillments` property, which is an array of fulfillment objects.
|
||||
|
||||
### Create a Fulfillment
|
||||
|
||||
You can create a fulfillment for a swap by sending a request to the [Create Swap Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapfulfillments):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.fulfillSwap(orderId, swapId, {
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminFulfillSwap } from "medusa-react"
|
||||
|
||||
const FulfillSwap = () => {
|
||||
const fulfillSwap = useAdminFulfillSwap(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleFulfill = () => {
|
||||
fulfillSwap.mutate({
|
||||
swap_id: swapId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default FulfillSwap
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/swaps/${swapId}/fulfillments`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/swaps/<SWAP_ID>/fulfillments' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order ID and swap ID as path parameters.
|
||||
|
||||
In the request body, you can pass optional parameters such as `metadata` or `no_notification`. These parameters will be used to create the fulfillment. You can learn more about available request body parameters in the [API reference](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapfulfillments).
|
||||
|
||||
The request returns the updated order as an object. You can access the order’s swaps in the `swaps` property, which is an array of swap objects.
|
||||
|
||||
### Create a Shipment
|
||||
|
||||
You can create a shipment for a swap’s fulfillment using the [Create Swap Shipment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapshipments):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.createSwapShipment(orderId, swapId, {
|
||||
fulfillment_id,
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateSwapShipment } from "medusa-react"
|
||||
|
||||
const CreateShipment = () => {
|
||||
const createShipment = useAdminCreateSwapShipment(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createShipment.mutate({
|
||||
swap_id,
|
||||
fulfillment_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateShipment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/swaps/${swapId}/shipments`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fulfillment_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/swaps/<SWAP_ID>/shipments' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'\
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"fulfillment_id": "<FUL_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint expects the order and swap IDs to be passed as path parameters.
|
||||
|
||||
In the request body, it’s required to pass the `fulfillment_id` parameter, which is the ID of the fulfillment the shipment is being created for. You can pass other optional parameters, such as an array of tracking numbers. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#tag/Orders/operation/PostOrdersOrderSwapsSwapShipments).
|
||||
|
||||
The request returns the updated order as an object. As mentioned before, a swap’s fulfillments can be accessed using the `fulfillments` property of a swap object. You can access the shipments, known as tracking links, of a fulfillment using the `tracking_links` property of a fulfillment object. The value of `tracking_links` is an array of tracking link objects.
|
||||
|
||||
You can alternatively access the tracking numbers using the `tracking_numbers` property of a fulfillment object, which is an array of strings.
|
||||
|
||||
You can access the status of a swap’s fulfillment using the `fulfillment_status` property of a swap object.
|
||||
|
||||
### Cancel a Fulfillment
|
||||
|
||||
:::note
|
||||
|
||||
You can’t cancel a fulfillment that has a shipment
|
||||
|
||||
:::
|
||||
|
||||
You can cancel a fulfillment by sending a request to the [Cancel Swap Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersswapfulfillmentscancel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.cancelSwapFulfillment(
|
||||
orderId,
|
||||
swapId,
|
||||
fulfillmentId
|
||||
)
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCancelSwapFulfillment } from "medusa-react"
|
||||
|
||||
const CancelFulfillment = () => {
|
||||
const cancelFulfillment = useAdminCancelSwapFulfillment(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCancel = () => {
|
||||
cancelFulfillment.mutate({
|
||||
swap_id,
|
||||
fulfillment_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CancelFulfillment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/swaps/${swapId}/shipments/${fulfillmentId}/cancel`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/swaps/<SWAP_ID>/fulfillments/<FUL_ID>/cancel' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order, swap, and fulfillment IDs to be passed as path parameters.
|
||||
|
||||
The request returns the updated order as an object. You can access the swaps using the `swaps` property of the order object, which is an array of swap objects.
|
||||
|
||||
You can check the fulfillment status of a swap using the `fulfillment_status` property of the swap object.
|
||||
|
||||
---
|
||||
|
||||
## Cancel Swap
|
||||
|
||||
:::note
|
||||
|
||||
You can’t cancel a swap that has been refunded. You must also cancel all swap’s fulfillments and return first.
|
||||
|
||||
:::
|
||||
|
||||
You can cancel a swap by sending a request to the [Cancel Swap endpoint](https://docs.medusajs.com/api/admin#orders_postordersswapcancel):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.cancelSwap(orderId, swapId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCancelSwap } from "medusa-react"
|
||||
|
||||
const CancelSwap = () => {
|
||||
const cancelSwap = useAdminCancelSwap(
|
||||
orderId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleCancel = () => {
|
||||
cancelSwap.mutate(swapId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CancelSwap
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders/${orderId}/swaps/${swapId}/cancel`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.swaps)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/orders/<ORDER_ID>/swaps/<SWAP_ID>/cancel' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order and swap IDs as path parameters.
|
||||
|
||||
The request returns the updated order as an object. You can access the swaps using the `swaps` property of the order object, which is an array of swap objects.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage orders using admin APIs](./manage-orders.mdx)
|
||||
- [How to manage returns using admin APIs](./manage-returns.mdx)
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
description: 'Learn how to handle the order claim event in the Medusa backend. When the event is triggered, you can send an email to the customer to inform them about it.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Handle Order Claim Event
|
||||
|
||||
In this document, you’ll learn how to handle the order claim event and send a confirmation email when the event is triggered.
|
||||
|
||||
## Overview
|
||||
|
||||
When a guest customer places an order, the order is not associated with a customer. It is associated with an email address.
|
||||
|
||||
After the customer registers, later on, they can claim that order by providing the order’s ID.
|
||||
|
||||
When the customer requests to claim the order, the event `order-update-token.created` is triggered on the Medusa backend. This event should be used to send the customer a confirmation email.
|
||||
|
||||
### What You’ll Learn
|
||||
|
||||
In this document, you’ll learn how to handle the `order-update-token.created` event on the backend to send the customer a confirmation email.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### Notification Provider
|
||||
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an existing plugin or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
This document has an example using the [SendGrid](../../../plugins/notifications/sendgrid.mdx) plugin.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create a Subscriber
|
||||
|
||||
To subscribe to and handle an event, you must create a subscriber.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about subscribers in the [Subscribers](../../../development/events/subscribers.mdx) documentation.
|
||||
|
||||
:::
|
||||
|
||||
Create the file `src/subscribers/claim-order.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/claim-order.ts
|
||||
type InjectedDependencies = {
|
||||
// TODO add necessary dependencies
|
||||
}
|
||||
|
||||
class ClaimOrderSubscriber {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// TODO subscribe to event
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimOrderSubscriber
|
||||
```
|
||||
|
||||
You’ll be adding in the next step the necessary dependencies to the subscriber.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about [dependency injection](../../../development/fundamentals/dependency-injection.md) in this documentation.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Subscribe to the Event
|
||||
|
||||
In this step, you’ll subscribe to the `order-update-token.created` event to send the customer a notification about their order edit.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
### Method 1: Using the NotificationService
|
||||
|
||||
If the notification provider you’re using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
|
||||
|
||||
```ts title=src/subscribers/claim-order.ts
|
||||
import { NotificationService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
notificationService: NotificationService
|
||||
}
|
||||
|
||||
class ClaimOrderSubscriber {
|
||||
constructor({ notificationService }: InjectedDependencies) {
|
||||
notificationService.subscribe(
|
||||
"order-update-token.created",
|
||||
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimOrderSubscriber
|
||||
```
|
||||
|
||||
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
:::
|
||||
|
||||
### Method 2: Using the EventBusService
|
||||
|
||||
If the notification provider you’re using isn’t configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
|
||||
|
||||
```ts title=src/subscribers/claim-order.ts
|
||||
import { EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
class ClaimOrderSubscriber {
|
||||
constructor({ eventBusService }: InjectedDependencies) {
|
||||
eventBusService.subscribe(
|
||||
"order-update-token.created",
|
||||
this.handleRequestClaimOrder
|
||||
)
|
||||
}
|
||||
|
||||
handleRequestClaimOrder = async (data) => {
|
||||
// TODO: handle event
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimOrderSubscriber
|
||||
```
|
||||
|
||||
When using this method, you’ll have to handle the logic of sending the confirmation email to the customer inside the handler function, which in this case is `handleRequestClaimOrder`.
|
||||
|
||||
The `handleRequestClaimOrder` event receives a `data` object as a parameter. This object holds the following properties:
|
||||
|
||||
1. `old_email`: The email associated with the orders.
|
||||
2. `new_customer_id`: The ID of the customer claiming the orders.
|
||||
3. `orders`: An array of the order IDs that the customer is requesting to claim.
|
||||
4. `token`: A verification token. This token is used to later verify the claim request and associate the order with the customer.
|
||||
|
||||
In this method, you should typically send an email to the customer’s old email. In the email, you should link to a page in your storefront and pass the `token` as a parameter.
|
||||
|
||||
The page would then send a request to the backend to verify that the `token` is valid and associate the order with the customer. You can read more about how to implement this in your storefront in [this documentation](../storefront/implement-claim-order.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Example: Using SendGrid
|
||||
|
||||
For example, you can implement this subscriber to send emails using SendGrid:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts title=src/subscribers/claim-order.ts
|
||||
import { EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService,
|
||||
sendgridService: any
|
||||
}
|
||||
|
||||
class ClaimOrderSubscriber {
|
||||
protected sendGridService: any
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
}: InjectedDependencies) {
|
||||
this.sendGridService = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"order-update-token.created",
|
||||
this.handleRequestClaimOrder
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
handleRequestClaimOrder = async (data) => {
|
||||
this.sendGridService.sendEmail({
|
||||
templateId: "order-claim-confirmation",
|
||||
from: "hello@medusajs.com",
|
||||
to: data.old_email,
|
||||
data: {
|
||||
link: `http://example.com/confirm-order-claim/${data.token}`,
|
||||
// other data...
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimOrderSubscriber
|
||||
```
|
||||
|
||||
Notice how the `token` is passed to the storefront link as a parameter.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement claim-order flow in your storefront](../storefront/implement-claim-order.mdx)
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
description: 'Learn how to send an order confirmation email to the customer. This guide uses SendGrid as an example Notification provider.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Send Order Confirmation Email
|
||||
|
||||
In this document, you’ll learn how to send an order confirmation email to the customer.
|
||||
|
||||
## Overview
|
||||
|
||||
When an order is placed, the `order.placed` event is triggered. You can listen to this event in a subscriber to perform an action, such as send the customer an order confirmation email.
|
||||
|
||||
This guide explains how to create the subscriber and how to use SendGrid to send the confirmation email. SendGrid is only used to illustrate how the process works, but you’re free to use any other notification service.
|
||||
|
||||
:::note
|
||||
|
||||
SendGrid is already configured to send emails when an order has been placed. So, by installing and configuring the plugin, you don't need to actually handle sending the order confirmation email. It's used as an example here to illustrate the process only.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It’s assumed you already have the Medusa backend installed. If not, you can either use the [create-medusa-app command](../../../create-medusa-app.mdx) to install different Medusa tools, including the backend, or [install the backend only](../../../development/backend/install.mdx).
|
||||
|
||||
### Event Bus Module
|
||||
|
||||
The event bus module trigger the event to the listening subscribers. So, it’s required to have an event bus module installed and configured on your Medusa backend.
|
||||
|
||||
The [Local Event Bus module](../../../development/events/modules/local.md) works in a development environment. For production, it’s recommended to use the [Redis Event Bus module](../../../development/events/modules/redis.md).
|
||||
|
||||
### Notification Provider
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Subscriber
|
||||
|
||||
To subscribe to and handle an event, you must create a subscriber.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
|
||||
|
||||
:::
|
||||
|
||||
Create the file `src/subscribers/order-confirmation.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/order-confirmation.ts
|
||||
type InjectedDependencies = {
|
||||
// TODO add necessary dependencies
|
||||
}
|
||||
|
||||
class OrderConfirmationSubscriber {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// TODO subscribe to event
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderConfirmationSubscriber
|
||||
```
|
||||
|
||||
You’ll be adding in the next step the necessary dependencies to the subscriber.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Subscribe to the Event
|
||||
|
||||
In this step, you’ll subscribe to the `order.placed` event to send the customer an order confirmation email.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
### Method 1: Using the NotificationService
|
||||
|
||||
If the notification provider you’re using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
|
||||
|
||||
```ts title=src/subscribers/order-confirmation.ts
|
||||
import { NotificationService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
notificationService: NotificationService
|
||||
}
|
||||
|
||||
class OrderConfirmationSubscriber {
|
||||
constructor({ notificationService }: InjectedDependencies) {
|
||||
notificationService.subscribe(
|
||||
"order.placed",
|
||||
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderConfirmationSubscriber
|
||||
```
|
||||
|
||||
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
:::
|
||||
|
||||
### Method 2: Using the EventBusService
|
||||
|
||||
If the notification provider you’re using isn’t configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
|
||||
|
||||
```ts title=src/subscribers/order-confirmation.ts
|
||||
import { EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
class OrderConfirmationSubscriber {
|
||||
constructor({ eventBusService }: InjectedDependencies) {
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrderConfirmation
|
||||
)
|
||||
}
|
||||
|
||||
handleOrderConfirmation = async (
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
// TODO: handle event
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderConfirmationSubscriber
|
||||
```
|
||||
|
||||
When using this method, you’ll have to handle the logic of sending the order confirmation email to the customer inside the handler function, which in this case is `handleOrderConfirmation`.
|
||||
|
||||
## Step 3: Handle the Event
|
||||
|
||||
The `handleOrderConfirmation` event receives a `data` object as a parameter. This object holds two properties:
|
||||
|
||||
- `id`: the ID of the order that was placed.
|
||||
- `no_notification`: a boolean value indicating whether the customer should receive notifications about the order or not.
|
||||
|
||||
In this method, you should typically send an email to the customer if `no_notification` is enabled.
|
||||
|
||||
To retrieve the order's details, you can add the `OrderService` into `InjectedDependencies` and use it within `handleOrderConfirmation`. For example:
|
||||
|
||||
```ts title=src/subscribers/order-confirmation.ts
|
||||
import { EventBusService, OrderService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
orderService: OrderService
|
||||
}
|
||||
|
||||
class OrderConfirmationSubscriber {
|
||||
protected readonly orderService_: OrderService
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
orderService,
|
||||
}: InjectedDependencies) {
|
||||
this.orderService_ = orderService
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrderConfirmation
|
||||
)
|
||||
}
|
||||
|
||||
handleOrderConfirmation = async (
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
const order = await this.orderService_.retrieve(data.id, {
|
||||
// you can include other relations as well
|
||||
relations: ["items"],
|
||||
})
|
||||
// TODO: handle event
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderConfirmationSubscriber
|
||||
```
|
||||
|
||||
After retrieving the order, you can add the logic necessary to send the email. In the email, you can include any content you want. For example, you can show the order's items or the order's status.
|
||||
|
||||
### Example: Using SendGrid
|
||||
|
||||
:::note
|
||||
|
||||
This example is only used to illustrate how the functionality can be implemented. As mentioned in the introduction, there's actually no need to implement this subscriber if you have the SendGrid plugin installed and configured, as it will automatically handle it.
|
||||
|
||||
:::
|
||||
|
||||
For example, you can implement this subscriber to send emails using SendGrid:
|
||||
|
||||
```ts title=src/subscribers/order-confirmation.ts
|
||||
import { EventBusService, OrderService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
orderService: OrderService
|
||||
sendgridService: any
|
||||
}
|
||||
|
||||
class OrderConfirmationSubscriber {
|
||||
protected readonly orderService_: OrderService
|
||||
protected readonly sendgridService_: any
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
orderService,
|
||||
sendgridService,
|
||||
}: InjectedDependencies) {
|
||||
this.orderService_ = orderService
|
||||
this.sendgridService_ = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrderConfirmation
|
||||
)
|
||||
}
|
||||
|
||||
handleOrderConfirmation = async (
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
const order = await this.orderService_.retrieve(data.id, {
|
||||
// you can include other relations as well
|
||||
relations: ["items"],
|
||||
})
|
||||
this.sendgridService_.sendEmail({
|
||||
templateId: "order-confirmation",
|
||||
from: "hello@medusajs.com",
|
||||
to: order.email,
|
||||
data: {
|
||||
// any data necessary for your template...
|
||||
items: order.items,
|
||||
status: order.status,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderConfirmationSubscriber
|
||||
```
|
||||
|
||||
Notice that you should replace the values in the object passed to the `sendEmail` method:
|
||||
|
||||
- `templateId`: Should be the ID of your order confirmation email template in SendGrid.
|
||||
- `from`: Should be the from email.
|
||||
- `to`: Should be the email associated with the order.
|
||||
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
description: "Learn about claims, how the claim process is implemented in the Medusa backend, and a claim’s relation to other entities."
|
||||
---
|
||||
|
||||
# Claims Architecture Overview
|
||||
|
||||
In this document, you’ll learn about claims, how the claim process is implemented in the Medusa backend, and a claim’s relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
An item that the customer ordered may be defected or does not match the original product they ordered. In those cases, a merchant can create a claim to handle this situation by either refunding the customer or replacing the item they got with a different one.
|
||||
|
||||
The Medusa core provides the necessary implementation and functionalities that allow you to integrate this process into your store.
|
||||
|
||||
---
|
||||
|
||||
## ClaimOrder Entity Overview
|
||||
|
||||
A claim is represented by the `ClaimOrder` entity. Some of its attributes include:
|
||||
|
||||
- `type`: a string indicating the type of the claim. Its value can be either `refund` or `replace`.
|
||||
- `payment_status`: a string indicating the status of the claim’s payment. Its possible values are indicated by the [ClaimPaymentStatus enum](../../references/entities/enums/ClaimPaymentStatus.md). This attribute is useful to check the status of the payment if the claim’s type is `refund`.
|
||||
- `fulfillment_status`: a string indicating the status of the claim’s fulfillment. Its possible values are indicated by the [ClaimFulfillmentStatus enum](../../references/entities/enums/ClaimFulfillmentStatus.md). This attribute is useful to check the status of the fulfillment if the claim’s type is `replace`.
|
||||
- `refund_amount`: an integer used to indicate the amount that should be refunded to the customer. This is only useful if the claim’s type is `refund`.
|
||||
- `canceled_at`: a date indicating when the claim was canceled.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full ClaimOrder entity in the entities reference](../../references/entities/classes/ClaimOrder.md).
|
||||
|
||||
---
|
||||
|
||||
## How are Claims Created
|
||||
|
||||
Claims are created in Medusa by an admin (typically a merchant). They are created on an order, and depending on the claim’s type the admin can specify details like the amount to be refunded, or the items to be returned and the new items to replace them.
|
||||
|
||||
You can create a claim either using the [Create Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaims) or using the `ClaimService`'s [create method](../../references/services/classes/ClaimService.md#create). This section explains the process within the Create Claim endpoint, with a focus on the `create` method.
|
||||
|
||||
### Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a claim. It is generated when the claim creation process is started by the admin using the [Create Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaims) and can be used to retry the claim creation safely if an error occurs. The idempotency key is stored in the `ClaimOrder` entity in the attribute `idempotency_key`.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
### Create Claim Endpoint Process
|
||||
|
||||
The following flow is implemented within the Create Claim endpoint:
|
||||
|
||||

|
||||
|
||||
1. When the idempotency key’s recovery point is `started`, the creation of the claim is started using the `ClaimService`'s [create method](../../references/services/classes/ClaimService.md#create). If the claim is created successfully, the idempotency key’s recovery point is changed to `claim_created`. In the `create` method:
|
||||
1. If the type of the claim is `refund` and no refund amount is set, the refund amount is calculated based on the items in the claim using the `ClaimService`'s [getRefundTotalForClaimLinesOnOrder method](../../references/services/classes/ClaimService.md#getrefundtotalforclaimlinesonorder).
|
||||
2. If new items are specified to send to the customer, new line items, which are represented by the `LineItem` entity, are generated using the `LineItemService`'s [generate method](../../references/services/classes/LineItemService.md#generate). These line items are later attached to the claim when it’s created under the `additional_items` relation. Also, the quantity of these items are reserved from the product variant’s inventory using the `ProductVariantInventoryService`'s [reserveQuantity method](../../references/services/classes/ProductVariantInventoryService.md#reservequantity).
|
||||
3. The claim is created and saved.
|
||||
4. If there were additional items attached to the claim, tax lines are created for these items using the `TaxProviderService`'s [createTaxLines method](../../references/services/classes/TaxProviderService.md#createtaxlines).
|
||||
5. If a shipping method was chosen to send the additional items to the customer, the shipping method is created using the `ShippingOptionService`'s [createShippingMethod method](../../references/services/classes/ShippingOptionService.md#createshippingmethod) or updated if it already exists using the `ShippingOptionService`'s [updateShippingMethod method](../../references/services/classes/ShippingOptionService.md#updateshippingmethod).
|
||||
6. A claim item is created for each of the items specified in the claim. These are the items that were originally in the order and that the claim was created for. The claim items are created using the `ClaimItemService`'s [create method](../../references/services/classes/ClaimItemService.md#create).
|
||||
7. If a return shipping method is specified, a return is created using the `ReturnService`'s [create method](../../references/services/classes/ReturnService.md#create).
|
||||
2. When the idempotency key’s recovery point is `claim_created`, if the claim’s type is `refund`, the refund is processed using the `ClaimService`'s [processRefund method](../../references/services/classes/ClaimService.md#processrefund). If the method is refunded successfully, the `payment_status` attribute of the claim is set to `refunded`. The refund is created directly on the order the claim belongs to. The recovery point of the idempotency key is changed to `refund_handled` at the end of this process.
|
||||
3. When the idempotency key’s recovery point is `refund_handled`, if the claim is associated with a return, the return is automatically fulfilled using the `ReturnService`'s [fulfill method](../../references/services/classes/ReturnService.md#fulfill) as it will be handled by the customer. The order associated with the claim is then returned and the idempotency key is set to `finished`.
|
||||
|
||||
---
|
||||
|
||||
## Fulfill a Claim
|
||||
|
||||
If a claim’s type is `replace`, an admin can create a [fulfillment](./fulfillments.md) for the additional items that should be sent to the customer.
|
||||
|
||||
A fulfillment can be created either using the [Create Claim Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimfulfillments) or the `ClaimService`'s [createFulfillment method](../../references/services/classes/ClaimService.md#createfulfillment).
|
||||
|
||||
:::note
|
||||
|
||||
The endpoint handles updating the inventory and reservations. So, if you choose to use the `createFulfillment` method, you should implement that within your code.
|
||||
|
||||
:::
|
||||
|
||||
By default, when a fulfillment is created, the claim’s `fulfillment_status` is set to `fulfilled`. However, if any of the item’s quantity isn’t fulfilled successfully, the `fulfillment_status` is set to `requires_action`.
|
||||
|
||||
After creating a fulfillment, you can create a shipment for the fulfillment either using the [Create Claim Shipment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderclaimsclaimshipments) or the `ClaimService`'s [createShipment method](../../references/services/classes/ClaimService.md#createshipment). If only some of the items and their quantities are shipped, the `fulfillment_status` of the claim is set to `partially_shipped`. Otherwise, if all items and quantities are shipped, the `fulfillment_status` of the claim is set to `shipped`.
|
||||
|
||||
You can alternatively cancel a fulfillment either using the [Cancel Claim Fulfillment endpoint](https://docs.medusajs.com/api/admin#tag/Orders/operation/PostOrdersClaimFulfillmentsCancel) or the `ClaimService`'s [cancelFulfillment method](../../references/services/classes/ClaimService.md#cancelfulfillment). This would change the `fulfillment_status` of the claim to `canceled`.
|
||||
|
||||
---
|
||||
|
||||
## Handling a Claim's Return
|
||||
|
||||
A claim's return can be marked as received, which would adjust the inventory and change the status of the return. This process is explained within the [Returns documentation](./returns.md#mark-return-as-received-process).
|
||||
|
||||
---
|
||||
|
||||
## Cancel a Claim
|
||||
|
||||
An admin can cancel a claim if it hasn’t been refunded either using the [Cancel Claim endpoint](https://docs.medusajs.com/api/admin#orders_postordersclaimcancel) or the `ClaimService`'s [cancel method](../../references/services/classes/ClaimService.md#cancel).
|
||||
|
||||
If any fulfillments were created, they must be canceled first. Similarly, if the claim is associated with a return, the return must be canceled first.
|
||||
|
||||
After the claim is canceled, the claim’s `fulfillment_status` is set to `canceled`.
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
This section includes relations that weren’t mentioned in other sections.
|
||||
|
||||
### Order
|
||||
|
||||
A claim belongs to an [order](./orders.md), which is the order it was created for. An order is represented by the `Order` entity.
|
||||
|
||||
You can access the order’s ID using the `order_id` attribute of the claim. You can also access the order by expanding the `order` relation and accessing `claim.order`.
|
||||
|
||||
### ClaimItem
|
||||
|
||||
A claim’s items are represented by the `ClaimItem` entity. A claim can have more than one claim item.
|
||||
|
||||
You can access a claim’s items by expanding the `claim_items` relation and accessing `claim.claim_items`.
|
||||
|
||||
### LineItem
|
||||
|
||||
A claim’s additional items that should be sent to the customer are represented by the `LineItem` entity. A claim can have more than one additional item.
|
||||
|
||||
You can access a claim’s additional items by expanding the `additional_items` relation and accessing `claim.additional_items`.
|
||||
|
||||
### Return
|
||||
|
||||
If a claim’s type is `replace`, it will be associated with a [return](./returns.md). A return is represented by the `Return` entity.
|
||||
|
||||
You can access the return by expanding the `return_order` relation and accessing `claim.return_order`.
|
||||
|
||||
### Address
|
||||
|
||||
If a claim’s type is `replace`, it can be associated with a shipping address. A shipping address is represented by the `Address` entity.
|
||||
|
||||
You can access the shipping address’s ID using the `shipping_address_id` of the claim. You can also access the shipping address by expanding the `shipping_address` relation and accessing `claim.shipping_address`.
|
||||
|
||||
### ShippingMethod
|
||||
|
||||
If a claim’s type is `replace`, it can be associated with more than one [shipping method](../carts-and-checkout/shipping.md#shipping-method) which are used to ship the additional items. A shipping method is represented by the `ShippingMethod` entity.
|
||||
|
||||
You can access the claim’s shipping methods by expanding the `shipping_methods` relation and accessing `claim.shipping_methods`.
|
||||
|
||||
### Fulfillment
|
||||
|
||||
If a claim’s type is `replace`, it can be associated with more than one fulfillment which are created to fulfill the additional items. A fulfillment is represented by the `Fulfillment` entity.
|
||||
|
||||
You can access the claim’s fulfillment by expanding the `fulfillments` relation and accessing `claim.fulfillments`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage claims using REST APIs](./admin/manage-claims.mdx)
|
||||
- [Orders architecture overview](./orders.md)
|
||||
- [Returns architecture overview](./claims.md)
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
description: "Learn about draft orders, process around draft orders, and their relation to other entities in the Medusa backend."
|
||||
---
|
||||
|
||||
# Draft Orders Architecture Overview
|
||||
|
||||
In this document, you’ll learn about draft orders, process around draft orders, and their relation to other entities in the Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Merchants may need to manually create orders without any involvement from the customer. This can be useful if the order is being created through a channel that isn’t integrated within your commerce system, or for some reason the customer can’t create the order themselves.
|
||||
|
||||
In Medusa, these types of orders are called draft orders. An admin or a merchant can create a draft order that holds all the details of the order. Then, the draft order can be later transformed into an actual order.
|
||||
|
||||
---
|
||||
|
||||
## DraftOrder Entity Overview
|
||||
|
||||
Some of the `DraftOrder`'s attributes include:
|
||||
|
||||
- `status`: a string indicating the status of the draft order. Its values can be:
|
||||
- `open`: the draft order is created, but hasn’t been completed.
|
||||
- `completed`: the draft order is completed. A draft order is considered completed when the payment for the order has been captured and an order has been created from the draft order.
|
||||
- `display_id`: a string that can be used as the displayed ID to customers and merchants.
|
||||
- `canceled_at`: a date indicating when the draft order was canceled.
|
||||
- `completed_at`: a date indicating when the draft order was completed.
|
||||
- `no_notification_order`: a boolean indicating whether the customer should receive notifications when the order is updated.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full DraftOrder entity in the entities reference](../../references/entities/classes/DraftOrder.md).
|
||||
|
||||
---
|
||||
|
||||
## How Draft Orders Work
|
||||
|
||||
You have full freedom in how you choose to implement creating draft orders. This section explains how it’s created in the Medusa backend using the [Create Draft Order](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders) and [Register Payment](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersdraftorderregisterpayment) endpoints.
|
||||
|
||||
A draft order is created using the `DraftOrderService`'s [create method](../../references/services/classes/DraftOrderService.md#create). Within that method, a cart is created along with it. The cart is used to store the order’s details, such as the draft order’s items, shipping options, and more. The cart has the type `draft_order`.
|
||||
|
||||
Since the draft order is associated with a cart, the process implemented in the Medusa backend around completing the draft order is pretty similar to that of completing a cart.
|
||||
|
||||
The payment must be authorized before the cart can be completed, which can be done using the `CartService`'s [authorizePayment method](../../references/services/classes/CartService.md#authorizepayment). Once the payment is authorized, the order can be created using the `OrderService`'s [createFromCart method](../../references/services/classes/OrderService.md#createfromcart).
|
||||
|
||||
:::note
|
||||
|
||||
In the Register Payment endpoint, the `system` payment method is used by default as the payment session of the cart. This means that the authorization and capturing of the payment don’t actually trigger any processes with existing payment processors integrated into your Medusa backend. It’s expected that the merchant will handle these processes manually.
|
||||
|
||||
:::
|
||||
|
||||
The draft order can then be completed using the `DraftOrderService`'s [registerCartCompletion method](../../references/services/classes/DraftOrderService.md#registercartcompletion). This would update its status to `completed` and would set the `order_id` attribute of the draft order. Finally, you can capture the payment of the order that was created using the `OrderService`'s [capturePayment method](../../references/services/classes/OrderService.md#capturepayment).
|
||||
|
||||
Once the order is created and the draft order is completed, the created order can be processed and handled the same as orders created by customers.
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
### Cart
|
||||
|
||||
A draft order is associated with a [cart](../carts-and-checkout/cart.md) that is used to set the items in the draft order, shipping method, and more. A cart is represented by the `Cart` entity.
|
||||
|
||||
You can access the ID of the draft order’s cart using the `cart_id` attribute. You can also access the cart by expanding the `cart` relation and accessing `draft_order.cart`.
|
||||
|
||||
### Order
|
||||
|
||||
A draft order is associated with an [order](./orders.md) that is created once the draft order is completed. An order is represented by the `Order` entity.
|
||||
|
||||
You can access the ID of the order using the `order_id` attribute. You can also access the order by expanding the `order` relation and accessing `draft_order.order`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage draft orders using REST APIs](./admin/manage-draft-orders.mdx)
|
||||
- [Order Architecture Overview](./orders.md)
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: "Learn about Fulfillments, how they’re used in your Medusa backend, and their relation to other entities."
|
||||
---
|
||||
|
||||
# Fulfillments Architecture Overview
|
||||
|
||||
In this document, you’ll learn about Fulfillments, how they’re used in your Medusa backend, and their relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
Fulfillments are used to ship items, typically to a customer. Fulfillments can be used in orders, returns, swaps, and more.
|
||||
|
||||
Fulfillments are processed within Medusa by a [fulfillment provider](../carts-and-checkout/backend/add-fulfillment-provider.md). The fulfillment provider handles creating, validating, and processing the fulfillment, among other functionalities. Typically, a fulfillment provider would be integrated with a third-party service that handles the actual shipping of the items.
|
||||
|
||||
When a fulfillment is created for one or more item, shipments can then be created for that fulfillment. These shipments can then be tracked using tracking numbers, providing customers and merchants accurate details about a shipment.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Entity Overview
|
||||
|
||||
Some of the `Fulfillment` entity’s attributes include:
|
||||
|
||||
- `provider_id`: a string indicating the ID of the fulfillment provider that processes this fulfillment. You can also access the provider by expanding the `provider` relation and accessing `fulfillment.provider`.
|
||||
- `location_id`: a string indicating where the fulfillment is being made from. When paired with the Stock Location module in the Medusa backend, this would be the ID of a `StockLocation`.
|
||||
- `no_notification`: a boolean value indicating whether the customer should receive notifications for fulfillment updates.
|
||||
- `data`: an object that can hold any data relevant for the fulfillment provider.
|
||||
- `shipped_at`: a date indicating when the fulfillment was shipped.
|
||||
- `canceled_at`: a date indicating when the fulfillment was canceled.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full Fulfillment entity in the entities reference](../../references/entities/classes/Fulfillment.md).
|
||||
|
||||
---
|
||||
|
||||
## TrackingLink Entity Overview
|
||||
|
||||
When a shipment is created from a fulfillment, it is represented by the `TrackingLink` entity. This entity has the following attributes:
|
||||
|
||||
- `tracking_number`: a string indicating a tracking number that allows customers and merchants to track the status of the shipment. Typically, this would be a tracking number retrieved from a third-party fulfillment service.
|
||||
- `url`: an optional string indicating a URL that can be used to track the shipment.
|
||||
- `fulfillment_id`: The ID of the fulfillment this tracking link is associated with. You can also access the fulfillment by expanding the `fulfillment` relation and accessing `tracking_link.fulfillment`.
|
||||
|
||||
You can access the tracking numbers of a fulfillment in the `tracking_numbers` attribute. You can also access the tracking links by expanding the `tracking_links` relation and accessing `fulfillment.tracking_link`.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillments in Other Processes
|
||||
|
||||
This section explains how a fulfillment can play a role in processes related to other entities and flows.
|
||||
|
||||
### Orders
|
||||
|
||||
A fulfillment is used to ship the items to the customer. Typically, the merchant would create a fulfillment, then create a shipment (tracking link) from that fulfillment.
|
||||
|
||||
If a fulfillment is created for an order, it’s associated with the order using the `order_id` attribute. You can also access the fulfillment’s order by expanding the `order` relation and accessing `fulfillment.order`.
|
||||
|
||||
A fulfillment’s status is stored in the `Order` entity’s `fulfillment_status` attribute, which can be one of the following values:
|
||||
|
||||
- `not_fulfilled`: no fulfillment has been created for the order.
|
||||
- `partially_fulfilled`: fulfillments have been created for some items in the order, but not all.
|
||||
- `fulfilled`: fulfillments have been created for all items in the order.
|
||||
- `partially_shipped`: shipments have been created for fulfillments, but not for all items in the order.
|
||||
- `shipped`: shipments have been created for fulfillments for all items in the order.
|
||||
- `partially_returned`: some items in the order have been returned.
|
||||
- `returned`: all items in the order have been returned.
|
||||
- `canceled`: the fulfillment has been canceled.
|
||||
- `requires_action`: a fulfillment has been created, but it requires some additional action.
|
||||
|
||||
You can learn more about how fulfillments are used in orders in the [Orders Architecture documentation](./orders.md#fulfillments-in-orders).
|
||||
|
||||
### Returns
|
||||
|
||||
A fulfillment is used to return the item from the customer. Typically, the fulfillment process for returns would be automated, as the customer is expected to handle it.
|
||||
|
||||
If a fulfillment is created for a return, it’s associated with the order and not with the return. The order’s `fulfillment_status` will be either `partially_returned` or `returned`, based on how many items were returned.
|
||||
|
||||
### Swaps
|
||||
|
||||
A fulfillment is used to send customers new items as part of a swap. Typically, the merchant would create a fulfillment, then create a shipment (tracking link) from that fulfillment.
|
||||
|
||||
If a fulfillment is created for a swap, it’s associated with the swap using the `swap_id` attribute. You can also access the fulfillment’s swap by expanding the `swap` relation and accessing `fulfillment.swap`.
|
||||
|
||||
A fulfillment’s status is stored in the `Swap` entity’s `fulfillment_status` attribute, which can be one of the following values:
|
||||
|
||||
- `not_fulfilled`: no fulfillment has been created for the swap.
|
||||
- `fulfilled`: a fulfillment has been created for the swap.
|
||||
- `partially_shipped`: shipments have been created for fulfillments, but not for all items in the swap.
|
||||
- `shipped`: shipments have been created for the fulfillment for all items in the swap.
|
||||
- `canceled`: the fulfillment has been canceled.
|
||||
- `requires_action`: the fulfillment has been created, but it requires some additional action.
|
||||
|
||||
You can learn more about fulfillments are used in swaps in the [Swaps Architecture documentation](./swaps.md#handling-swap-fulfillment).
|
||||
|
||||
### Claims
|
||||
|
||||
A fulfillment is used to send customers additional items as part of a claim. Typically, the merchant would create a fulfillment, then create a shipment (tracking link) from that fulfillment.
|
||||
|
||||
If a fulfillment is created for a claim, it’s associated with the claim using the `claim_order_id` attribute. You can also access the fulfillment’s claim by expanding the `claim_order` relation and accessing `fulfillment.claim_order`.
|
||||
|
||||
A fulfillment’s status is stored in the `Claim` entity’s `fulfillment_status` attribute, which can be one of the following values:
|
||||
|
||||
- `not_fulfilled`: no fulfillment has been created for the claim.
|
||||
- `partially_fulfilled`: fulfillments have been created for some items in the claim, but not all.
|
||||
- `fulfilled`: fulfillments have been created for all the items in the claim.
|
||||
- `partially_shipped`: shipments have been created for fulfillments, but not for all items in the claim.
|
||||
- `shipped`: shipments have been created for the fulfillment for all items in the claim.
|
||||
- `canceled`: the fulfillment has been canceled.
|
||||
- `requires_action`: the fulfillment has been created, but it requires some additional action.
|
||||
|
||||
You can learn more about fulfillments are used in claims in the [Claims Architecture documentation](./claims.md#fulfill-a-claim).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Orders architecture overview](./orders.md)
|
||||
- [Swaps architecture overview](./swaps.md)
|
||||
- [Claims architecture overview](./claims.md)
|
||||
- [Returns architecture overview](./returns.md)
|
||||
@@ -0,0 +1,302 @@
|
||||
---
|
||||
description: "Learn about the orders architecture, how they’re created, and their relation to other entities."
|
||||
---
|
||||
|
||||
# Orders Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the orders architecture, how they’re created, and their relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
Orders are placed by customers who purchase items from your store. They involve intricate commerce operations related to inventory, fulfillment, payment, and more.
|
||||
|
||||
Medusa supports order features such as order editing, creating swaps for orders, returning orders, and more. These features allow merchants to handle and automate Return Merchandise Authorization (RMA) flows.
|
||||
|
||||
:::note
|
||||
|
||||
As the Order domain is large in features and details, some features such as Returns or Swaps will not be discussed within this documentation page. Instead, they’ll be discussed in their own documentation pages.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Order Entity Overview
|
||||
|
||||
Some of the attributes of the `Order` entity include:
|
||||
|
||||
- `fulfillment_status`: a string indicating the status of the order’s fulfillment. Its possible values can determine whether all items have been fulfilled, shipped, or returned.
|
||||
- `payment_status`: a string indicating the status of the order’s payment. Its possible values can determine whether the payment of the order has been captured or refunded.
|
||||
- `status`: a string indicating the overall status of the order. Its values can be:
|
||||
- `pending`: this is the default status of the order after it has been created.
|
||||
- `completed`: the order is marked as completed. A merchant typically marks the order as completed after the order has been fulfilled and paid.
|
||||
- `refunded`: the order has been refunded.
|
||||
- `archived`: the order is archived. This status can only be attained after the order has been completed or refunded.
|
||||
- `canceled`: the order has been canceled. An order can’t be canceled if it has been refunded or if any of its relations, such as fulfillment or swaps, are not canceled.
|
||||
- `display_id`: a string indicating an incremental ID that can be displayed to the customer or internally to merchants.
|
||||
- `canceled_at`: a date indicating when the order was canceled.
|
||||
- `no_notification`: a boolean value indicating whether the customer should receive notifications when the order is updated.
|
||||
- `external_id`: a string indicating an ID of the order in an external system. This can be useful if you’re migrating your orders from another commerce system or you’re linking your order to a third-party service.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the full [Order entity in the entities reference](../../references/entities/classes/Order.md).
|
||||
|
||||
---
|
||||
|
||||
## How are Orders Created
|
||||
|
||||
You have full freedom in how you create your orders. Within the Medusa backend, there are two defined ways when an order is created:
|
||||
|
||||
1. Using a cart: The customer adds products to their cart and go through the checkout process to place the order. Learn about the [cart completion process here](../carts-and-checkout/cart.md#cart-completion-process).
|
||||
2. Using draft orders: the merchant can create draft orders without the customer’s involvement. This includes a very similar process of adding products to the draft order, supplying the shipping and billing addresses, and more. The draft order can later be turned into a regular order.
|
||||
|
||||
---
|
||||
|
||||
## Payments in Orders
|
||||
|
||||
In the cart completion process, or when you use the `OrderService`'s method [createFromCart](../../references/services/classes/OrderService.md#createfromcart), the cart’s payment is also associated with the order by setting the `order_id` of the payment to the newly created order’s ID.
|
||||
|
||||
An order can have more than one payment. You can access the order’s payments by expanding the `payments` relation and accessing `order.payments`.
|
||||
|
||||
By default, the payment will be authorized but not captured. Some payment processor plugins, such as the Stripe plugin, allow changing this behavior to automatically capture the payment. You can also do that within your custom payment processor.
|
||||
|
||||
In the default scenario, the merchant would have to capture that payment manually, which would change the `payment_status` of the order. The payment can be captured using the `PaymentService`'s [capture method](../../references/services/classes/PaymentService.md#capture).
|
||||
|
||||
After a payment has been captured, it can be refunded either fully or a specific amount of it. This is useful if items of an order has been returned or swapped, or if an order has been edited. The payment can be refunded using the `PaymentService`'s [refund method](../../references/services/classes/PaymentService.md#refund).
|
||||
|
||||
The Medusa backend also provides payment admin APIs that you can use to retrieve, capture, and refund the payment.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillments in Orders
|
||||
|
||||
After an order is placed, you can create [fulfillments](./fulfillments.md) for the items in the order. You can fulfill all items or some items. A fulfillment is represented by the `Fulfillment` entity, and it’s associated with the order through the `order_id` attribute of the fulfillment.
|
||||
|
||||
Creating fulfillments changes the `fulfillment_status` of the order. If all items were fulfilled, the status changes to `fulfilled`. If only some items are fulfilled, the status changes to `partially_fulfilled`.
|
||||
|
||||
You can access an order’s fulfillments by expanding the `fulfillments` relation and accessing `order.fulfillments`.
|
||||
|
||||
After a fulfillment is created, you can mark it as shipped. This would change the `fulfillment_status` of the order to `shipped` if all items were shipped, or `partially_shipped` if only some items were shipped.
|
||||
|
||||
A fulfillment can instead be canceled, changing the `fulfillment_status` to `canceled`.
|
||||
|
||||
If one or some items in an order are returned, the `fulfillment_status` is set to `partially_returned`. If all items were returned, the `fulfillment_status` is set to `returned`.
|
||||
|
||||
The Medusa backend provides these functionalities through the admin APIs. You can also use the `OrderService`'s methods to perform these functionalities in a custom flow, such as the [createFulfillment](../../references/services/classes/OrderService.md#createfulfillment) or [createShipment](../../references/services/classes/OrderService.md#createshipment) methods.
|
||||
|
||||
---
|
||||
|
||||
## Order Totals Calculations
|
||||
|
||||
By default, the `Order` entity doesn’t hold any details regarding the totals. These are computed and added to the order instance using the `OrderService`'s [decorateTotals method](../../references/services/classes/OrderService.md#decoratetotals). There's also a dedicated method in the `OrderService`, [retrieveWithTotals](../../references/services/classes/OrderService.md#retrievewithtotals), attaching the totals to the order instance automatically. It is recommended to use this method by default when you need to retrieve the order.
|
||||
|
||||
The order’s totals are calculated based on the content and context of the order. This includes the order’s region, whether tax-inclusive pricing is enabled, the chosen shipping methods, and more.
|
||||
|
||||
The calculated order’s totals include:
|
||||
|
||||
- `shipping_total`: The total of the chosen shipping methods, with taxes.
|
||||
- `discount_total`: The total of the applied discounts.
|
||||
- `raw_discount_total`: The total of the applied discounts without rounding.
|
||||
- `item_tax_total`: The total applied taxes on the order’s items.
|
||||
- `gift_card_total`: The total gift card amount applied on the order. If there are any taxes applied on the gift cards, they’re deducted from the total.
|
||||
- `gift_card_tax_total`: The total taxes applied on the order’s gift cards.
|
||||
- `tax_total`: The total taxes applied (the sum of `shipping_tax_total`, `item_tax_total`, and `gift_card_tax_total`).
|
||||
- `subtotal`: The total of the items without taxes or discounts.
|
||||
- `refunded_total`: the total amount refunded to the customer, if any.
|
||||
- `paid_total`: the total amount paid by the customer.
|
||||
- `refundable_amount`: the amount that can be refunded to the customer. This would be the subtraction of `refunded_total` from `paid_total`.
|
||||
- `total`: The overall total of the order.
|
||||
|
||||
:::note
|
||||
|
||||
If you have tax-inclusive pricing enabled, you can learn about other available total fields [here](../taxes/inclusive-pricing.md#retrieving-tax-amounts).
|
||||
|
||||
:::
|
||||
|
||||
The order’s totals are retrieved by default in all the order’s [store](https://docs.medusajs.com/api/store#orders) and [admin](https://docs.medusajs.com/api/admin#orders) APIs.
|
||||
|
||||
---
|
||||
|
||||
## Order Edits
|
||||
|
||||
After an order has been placed, it can be edited to add, remove, or change ordered items.
|
||||
|
||||
:::tip
|
||||
|
||||
Order Edits don't cover edits to other details in the order, such as the shipping address, as these can be done by the admin at any point. This is only focused on the line items in the cart.
|
||||
|
||||
:::
|
||||
|
||||
Typically, a merchant would edit the order and send the edit request to the customer with the changes being made. Any additional payments required can be made when the customer accepts the order edit.
|
||||
|
||||
A merchant may also choose to force the edit on the order, by-passing the customer’s confirmation.
|
||||
|
||||

|
||||
|
||||
Although this process is implemented in this flow within the Medusa backend, there is no requirement for you to actually follow it. For example, you can allow the customer or a third-party service to create and manage the order edit.
|
||||
|
||||
The Medusa backend provides the [order edit admin APIs](https://docs.medusajs.com/api/admin#order-edits), but you can also use the [OrderEditService](../../references/services/classes/OrderEditService.md) to perform the same functionalities in a custom flow.
|
||||
|
||||
Order edits are represented by the `OrderEdit` entity. This entity is linked to the order through the `order_id` attribute. You can access an order’s edits by expanding the `edits` relation and accessing `order.edits`. Notice that an order can have multiple edits during its lifecycle, but it can’t have more than one ongoing edit.
|
||||
|
||||
Some of the `OrderEdit`'s other attributes include:
|
||||
|
||||
- `internal_note`: a string that can hold a note to be visible only internally.
|
||||
- `created_by`: a string that typically should hold the ID of who created the order edit. For example, if the merchant created the order edit, it should hold the ID of a `User`. There are no restrictions on what this attribute can hold, so you can also add here third-party IDs or names if necessary.
|
||||
- `requested_by`: a string that typically should hold the ID of who requested the order edit. Similar to the `created_by` attribute, there are no restrictions on what value this attribute can actually hold.
|
||||
- `confirmed_by`: a string that typically should hold the ID of who confirmed the order edit. Similar to the `created_by` attribute, there are no restrictions on what value this attribute can actually hold.
|
||||
- `declined_by`: a string that typically should hold the ID of who declined the order edit. Similar to the `created_by` attribute, there are no restrictions on what value this attribute can actually hold.
|
||||
|
||||
There are other attributes explained in other sections. You can also check out the full [OrderEdit entity in the entities reference](../../references/entities/classes/OrderEdit.md).
|
||||
|
||||
### Item Changes
|
||||
|
||||
Changes to the items in the orders are stored in the `OrderItemChange` entity. Some of this entity’s attributes include:
|
||||
|
||||
- `order_edit_id`: The ID of the order edit this item change is linked to. The order edit can also be accessed by expanding the `order_edit` relation and accessing `itemChange.order_edit`.
|
||||
- `type`: a string indicating the type of change being made. Its value can be:
|
||||
- `item_add` meaning that a new item is being added to the order.
|
||||
- `item_remove` meaning that an item in the order is being removed.
|
||||
- `item_update` meaning that an item in the order is being updated. For example, its quantity is being changed.
|
||||
- `original_line_item_id`: The ID of the line item that this item change is targeting. This would be the item that should be deleted or updated. If a new item is being added, then this attribute will be null. The line item can also be accessed by expanding the `original_line_item` relation and accessing `itemChange.original_line_item`.
|
||||
- `line_item_id`: the ID of the new line item to replace the original one. This line item would hold the changes that should be applied on the original line item. The line item can also be accessed by expanding the `line_item` relation and accessing `itemChange.line_item`.
|
||||
|
||||
When the order edit is confirmed, the original line items are no longer linked to the order. On the other hand, the new line items are linked to the new order. The items’ history is preserved through the order edits. So, you can access the previous state of an order and its items by accessing `order.edits`.
|
||||
|
||||
### PaymentCollection
|
||||
|
||||
If the order edit requires additional payments from the customer, they are stored in a payment collection. A payment collection allows bundling more than one payment related to a single entity or flow. It is represented by the `PaymentCollection` entity, which has the following relations:
|
||||
|
||||
- `payment_sessions`: the payment sessions that are linked to the payment collection. Payment sessions are linked to a payment provider and are used to hold the status of a payment in a flow, such as the checkout flow.
|
||||
- `payments`: the payments that are linked to the payment collection. Payments are authorized amounts by the customer that can later be processed. For example, you can capture or refund a payment.
|
||||
|
||||
For order edits, you can authorize the entire payment collection that holds additional required payments when the customer confirms the order edit.
|
||||
|
||||
---
|
||||
|
||||
## Automating RMA Flows
|
||||
|
||||
Medusa provides the necessary infrastructure and tooling that allows automating RMA flows. Entities involved in the RMA flows can include:
|
||||
|
||||
- Returns: Return an item from the customer to the merchant.
|
||||
- Swap: Swap an item with another. This involves returning the original item from the customer and shipping a new item to the customer.
|
||||
- Claim: Allow a customer to refund or replace an item in their order if it’s faulty or for other reasons.
|
||||
|
||||
The Medusa backend facilitates automating these flows by allowing the customer to submit a [return](https://docs.medusajs.com/api/store#returns_postreturns) or [swap](https://docs.medusajs.com/api/store#swaps_postswaps) requests through the store APIs. The merchant can then review and handle these requests. This eliminates the need for the customer to perform the same action through customer support or other means.
|
||||
|
||||
You can also integrate these flows within bigger processes that trigger requesting or creating these flows. It can be done through core APIs, [custom endpoints](../../development/endpoints/overview.mdx), or [custom services](../../development/services/overview.mdx). You can also listen to events related to orders such as [Order](../../development/events/events-list.md#order-events) or [Swap](../../development/events/events-list.md#swap-events) events with [subscribers](../../development/events/subscribers.mdx) to perform asynchronous actions.
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
This section includes relations that weren’t mentioned in other sections.
|
||||
|
||||
### Cart
|
||||
|
||||
An order can be associated with a [cart](../carts-and-checkout/overview.mdx), which is the cart it is created from. A cart is represented by the `Cart` entity.
|
||||
|
||||
You can access the cart’s ID using the `cart_id` attribute. You can also access the cart by expanding the `cart` relation and accessing `order.cart`.
|
||||
|
||||
### LineItem
|
||||
|
||||
An order has items, which are represented by the `LineItem` entity. These are typically the same line items created in the cart, with the `order_id` attribute of the line item set to the ID of the order.
|
||||
|
||||
You can access an order’s items by expanding the `items` relation and accessing `order.items`.
|
||||
|
||||
### Customer
|
||||
|
||||
An order is associated with the [customer](../customers/overview.mdx) that placed the order. A customer is represented by the `Customer` entity.
|
||||
|
||||
You can access the customer’s ID using the `customer_id` attribute. You can also access the customer by expanding the `customer` relation and accessing `order.customer`.
|
||||
|
||||
### SalesChannel
|
||||
|
||||
An order can belong to a [sales channel](../sales-channels/overview.mdx). This typically would be the sales channel of the cart the order was created from. A sales channel is represented by the `SalesChannel` entity.
|
||||
|
||||
The sales channel’s ID is stored in the `sales_channel_id` attribute of the order. You can also access the sales channel by expanding the `sales_channel` relation and accessing `order.sales_channel`.
|
||||
|
||||
### Region
|
||||
|
||||
An order belongs to a [region](../regions-and-currencies/overview.mdx). Typically, this would be the region of the cart the order is created from. A region is represented by the `Region` entity.
|
||||
|
||||
You can access the region’s ID using the `region_id` attribute. You can also access the region by expanding the `region` relation and accessing `order.region`.
|
||||
|
||||
### Currency
|
||||
|
||||
An order is associated to a [currency](../regions-and-currencies/overview.mdx). Typically, this would be the currency of the region the order belongs to. A currency is represented by the `Currency` entity.
|
||||
|
||||
You can access the currency code using the `currency_code` attribute. You can also access the currency by expanding the `currency` relation and accessing `order.currency`.
|
||||
|
||||
### Billing Address
|
||||
|
||||
An order can have a billing address, which is represented by the `Address` entity.
|
||||
|
||||
You can access the billing address’s ID using the `billing_address_id` attribute. You can also access the billing address by expanding the `billing_address` relation and accessing `order.billing_address`.
|
||||
|
||||
### Shipping Address
|
||||
|
||||
An order can have a shipping address, which is represented by the `Address` entity.
|
||||
|
||||
You can access the shipping address’s ID using the `shipping_address_id` attribute. You can also access the shipping address by expanding the `shipping_address` relation and accessing `order.shipping_address`.
|
||||
|
||||
### Discount
|
||||
|
||||
[Discounts](../discounts/overview.mdx) can be applied on an order. Typically, these would be the discounts that were applied on the cart associated with the order. A discount is represented by the `Discount` entity.
|
||||
|
||||
You can access the order’s discounts by expanding the `discounts` relation and accessing `order.discounts`.
|
||||
|
||||
### GiftCard
|
||||
|
||||
[Gift cards](../gift-cards/overview.mdx) can be applied on an order. Typically, these would be the gift cards that were applied on the cart associated with the order. A gift card is represented by the `GiftCard` entity.
|
||||
|
||||
You can access the order’s gift cards by expanding the `gift_cards` relation and accessing `order.gift_cards`.
|
||||
|
||||
### GiftCardTransaction
|
||||
|
||||
A gift card transaction is created when a gift card is used on the cart. It is used to deduct an amount from the original balance of the gift card after the order is placed, and to keep track of the history of a gift card’s transactions. It’s represented by the `GiftCardTransaction` entity and it’s associated with the order’s ID using the `order_id` attribute on the entity.
|
||||
|
||||
You can access an order’s gift card transactions by expanding the `gift_card_transactions` relation and accessing `order.gift_card_transactions`.
|
||||
|
||||
### ShippingMethod
|
||||
|
||||
An order is associated with [shipping methods](../carts-and-checkout/shipping.md#shipping-method). Typically, these would be the shipping methods chosen during checkout. An order can have more than one shipping method. A shipping method is represented by the `ShippingMethod` entity.
|
||||
|
||||
You can access the order’s shipping method by expanding the `shipping_methods` relation and accessing `order.shipping_methods`.
|
||||
|
||||
### Returns
|
||||
|
||||
An order can be associated with more than one [return](./returns.md). For example, a customer may request to return items separately or gradually. A return is represented by the `Return` entity.
|
||||
|
||||
You can access the order’s returns by expanding the `returns` relation and accessing `order.returns`.
|
||||
|
||||
### ClaimOrder
|
||||
|
||||
An order can be associated with more than one [claim](./claims.md). A claim is represented by the `ClaimOrder` entity.
|
||||
|
||||
You can access the order’s claims by expanding the `claims` relation and accessing `order.claims`.
|
||||
|
||||
### Refund
|
||||
|
||||
An order can be associated with more than one refund. A refund is represented by the `Refund` entity.
|
||||
|
||||
You can access the order’s refunds by expanding the `refunds` relation and accessing `order.refunds`.
|
||||
|
||||
### Swap
|
||||
|
||||
An order can be associated with more than one [swap](./swaps.md). A swap is represented by the `Swap` entity.
|
||||
|
||||
You can access the order’s swaps by expanding the `swap` relation and accessing `order.swap`.
|
||||
|
||||
### DraftOrder
|
||||
|
||||
An order can be associated with a [draft order](./draft-orders.md). This would be the draft order that the order was created from.
|
||||
|
||||
The draft order’s ID is stored in the `draft_order_id` attribute. You can also access the draft order by expanding the `draft_order` relation and accessing `order.draft_order`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage orders](./admin/manage-orders.mdx)
|
||||
- [How to edit an order](./admin/edit-order.mdx)
|
||||
- [How to handle order edits on the storefront](./storefront/handle-order-edits.mdx)
|
||||
- [How to Implement Claim Order Flow in the storefront](./storefront/implement-claim-order.mdx)
|
||||
@@ -0,0 +1,343 @@
|
||||
---
|
||||
description: "Customers place orders to purchase products from an ecommerce business. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Orders
|
||||
|
||||
Customers place orders to purchase products from an ecommerce business. This overview introduces the available features related to orders.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Orders user guide](../../user-guide/orders/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Order Management
|
||||
|
||||
Admins can manage orders placed by customers, including capturing payment and fulfilling items in the order. Admins can also export orders into CSV files.
|
||||
|
||||
Customers can view their previous orders.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/manage-orders',
|
||||
label: 'Admin: Manage Orders',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage orders using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/manage',
|
||||
label: 'User Guide: Manage Orders',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage orders in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/export',
|
||||
label: 'User Guide: Export Orders',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Export orders into CSV files in Medusa admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/payments',
|
||||
label: 'User Guide: Manage Payments',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage an order\'s payment in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/fulfillments',
|
||||
label: 'User Guide: Manage Fulfillment',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage an order\'s fulfillment in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/storefront/retrieve-order-details',
|
||||
label: 'Storefront: Retrieve Order Details',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: "Learn the different ways to retrieve an order's details",
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Edit Orders
|
||||
|
||||
Admins can perform edits on items in an order, such as add, remove, or update items. Admins can request confirmation from the customer or force apply the edit.
|
||||
|
||||
Customers can review order edit requests and authorize additional payments if necessary.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/edit-order',
|
||||
label: 'Admin: Edit an Order',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to edit an order using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/edit',
|
||||
label: 'User Guide: Edit Order',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Edit an order in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/storefront/handle-order-edits',
|
||||
label: 'Storefront: Order Edits',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Handle order edits in a storefront.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Draft Orders
|
||||
|
||||
Admins can create draft orders that don’t require involvement from a customer. They can specify all details including items in the order, payment method, and more.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/manage-draft-orders',
|
||||
label: 'Admin: Manage Draft Orders',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage draft orders using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/edit',
|
||||
label: 'User Guide: Draft Orders',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage draft orders in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/admin#draft-orders_getdraftorders',
|
||||
label: 'Admin APIs: Draft Orders',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Admin REST APIs for Draft Orders.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Returns, Swaps, and Claims
|
||||
|
||||
Admins can return order items, or create swaps to return an item to be replaced by a new one.
|
||||
|
||||
Customers can also request to return or swap items. This allows for implementing an automated Return Merchandise Authorization (RMA) flow.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/manage-returns',
|
||||
label: 'Admin: Manage Returns',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage returns using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/manage-swaps',
|
||||
label: 'Admin: Manage Swaps',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage swaps using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/manage-claims',
|
||||
label: 'Admin: Manage Claims',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage claims using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/returns',
|
||||
label: 'User Guide: Manage Returns',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage order returns in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/exchange',
|
||||
label: 'User Guide: Exchanges',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage order exchanges in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/claims',
|
||||
label: 'User Guide: Manage Claims',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage order claims in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/storefront/create-return',
|
||||
label: 'Storefront: Create a Return',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a return as a customer.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/storefront/create-swap',
|
||||
label: 'Storefront: Create a Swap',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a swap as a customer.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#returns',
|
||||
label: 'Storefront APIs: Returns',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Storefront REST APIs for Returns.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how order-related entities are built, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders',
|
||||
label: 'Architecture: Order',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Order architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/swaps',
|
||||
label: 'Architecture: Swap',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Swap architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/returns',
|
||||
label: 'Architecture: Return',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Return architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/claims',
|
||||
label: 'Architecture: Claim',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Claim architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/draft-orders',
|
||||
label: 'Architecture: Draft Order',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Draft Order architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/fulfillments',
|
||||
label: 'Architecture: Fulfillment',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Fulfillment architecture.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Order’s relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Carts and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Customers can place an order using a virtual cart.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Products',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Customers can browse products to purchase them.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/overview',
|
||||
label: 'Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['channels-solid'],
|
||||
description: 'Orders can be associated with specific channels.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
description: "Learn about returns, how the return process is implemented in the Medusa backend, and a return’s relation to other entities."
|
||||
---
|
||||
|
||||
# Returns Architecture Overview
|
||||
|
||||
In this document, you’ll learn about returns, how the return process is implemented in the Medusa backend, and a return’s relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
After a customer receives an item they ordered, they might want to return it back. This process includes returning the item into the commerce store, and refunding the payment back to the customer.
|
||||
|
||||
The Medusa core provides the necessary implementation and functionalities that allow you to integrate this process into your store and automate the Return Merchandise Authorization (RMA) flow.
|
||||
|
||||
---
|
||||
|
||||
## Return Entity Overview
|
||||
|
||||
Some of the `Return` entity’s attributes include:
|
||||
|
||||
- `status`: A string indicating the status of the return. Its value can be one of the following:
|
||||
- `requested`: The customer requested the return.
|
||||
- `requires_action`: The return couldn’t be marked as received because there’s a mismatch between the items specified in the return request and the received items.
|
||||
- `received`: the merchant marks the return as received.
|
||||
- `canceled`: the merchant canceled the return.
|
||||
- `refund_amount`: A number indicating the amount that should be refunded.
|
||||
- `received_at`: the date indicating when the return was marked as received.
|
||||
- `no_notification`: a boolean value indicating whether the customer should receive notification updates when there are any changes in the return.
|
||||
- `shipping_data`: this is a JSONB object that can hold any data related to the fulfillment associated with the return.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full Return entity in the entities reference](../../references/entities/classes/Return.md).
|
||||
|
||||
---
|
||||
|
||||
## Returns Process
|
||||
|
||||
Returns can be created in three ways:
|
||||
|
||||
1. Created directly, typically by a client or customer.
|
||||
2. Created automatically and linked to a swap when a swap is created. You can learn more about the overall swap process in the [Swaps documentation](./swaps.md). The Mark Return as Received section applies for swaps as well.
|
||||
3. Created automatically and linked to a claim when a claim is created. You can learn more about the overall claim process in the [Claims documentation] The Mark Return as Received section applies for claims as well.
|
||||
|
||||
### Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a return. It is generated when the return creation process is started by the customer and can be used to retry the return creation safely if an error occurs. The idempotency key is stored in the `Return` entity in the attribute `idempotency_key`.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
### Return Creation Process Through the Customer
|
||||
|
||||
This section explains how the return is created by the customer or any type of client. This is the process created within the Medusa core.
|
||||
|
||||
The following process occurs within the Create Return storefront endpoint:
|
||||
|
||||

|
||||
|
||||
1. The customer creates a return, specifying the items they want to return and optionally the shipping option they want to use to return the items. This is done using the `ReturnService`'s [create method](../../references/services/classes/ReturnService.md#create).
|
||||
2. If they specify the return shipping option they want to use, a shipping method is created from the chosen shipping option and the return is automatically fulfilled in the backend using the `ReturnService`'s [fulfill method](../../references/services/classes/ReturnService.md#fulfill).
|
||||
|
||||
After this process, the return will be available for the merchant or the admin to view and handle it. The merchant can either mark the return as received or cancel the return.
|
||||
|
||||
### Mark Return as Received Process
|
||||
|
||||
Marking a return as received would refund the amount to the customer and adjust the store’s inventory for the returned items.
|
||||
|
||||
The following process occurs within the [Receive Return admin endpoint](https://docs.medusajs.com/api/admin#returns_postreturnsreturnreceive):
|
||||
|
||||

|
||||
|
||||
1. The return is marked as received using the `ReturnService`'s receive method. In this method:
|
||||
1. The return’s status is checked to ensure it’s not canceled or received. If so, the process is terminated with an error.
|
||||
2. The received items are validated to ensure they match the items that were previously requested to be returned. If there’s a mismatch, the return’s status is set to `requires_more`. This is useful in situations where a custom refund amount is requested, but the returned items don’t match the requested items.
|
||||
3. If there’s no mismatch issue, the return’s status is set to `received`.
|
||||
4. The inventory of the returned items is adjusted using the `ProductVariantInventoryService`'s [method adjustInventory](../../references/services/classes/ProductVariantInventoryService.md#adjustinventory)
|
||||
2. After the return is marked as received, if the return is associated with a swap, the `SwapService`'s [registerReceived method](../../references/services/classes/SwapService.md#registerreceived) is used to validate the return, emit the swap event `received`, and return the swap.
|
||||
3. On the other hand, after the return is marked as received, if the return is associated with an order, the `OrderService`'s [registerReturnReceived method](../../references/services/classes/OrderService.md#registerreturnreceived). In this method:
|
||||
1. If the amount that is expected to be refunded is greater than the amount that can be refunded, the `fulfillment_status` of the order is set to `requires_action`. And the process is terminated.
|
||||
2. If there are no validation issues, the payment is refunded and the fulfillment status is set to `returned` if all items of the order were returned, or `partially_returned` if only some items were returned.
|
||||
|
||||
### Cancel a Return
|
||||
|
||||
The merchant can cancel a return if it hasn’t be marked as `received` before. This can be done either using the `ReturnService`'s [cancel method](../../references/services/classes/ReturnService.md#cancel) or using the [Cancel Return endpoint](https://docs.medusajs.com/api/admin#returns_postreturnsreturncancel).
|
||||
|
||||
Canceling a return would change its status to canceled.
|
||||
|
||||
---
|
||||
|
||||
## RMA Automation with Returns
|
||||
|
||||
Giving the client control over the creation of a return allows businesses to automate their RMA flow.
|
||||
|
||||
The client, typically the customer, creates the return, and specifies the shipping option they’ll use to return the item. The admin, typically the merchant, can view the requested return, mark it as received, and refund the payment to the customer. The core will also take care of adjusting the inventory automatically.
|
||||
|
||||

|
||||
|
||||
This removes any friction and support required between the client and the admin.
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
This section includes relations that weren’t mentioned in other sections.
|
||||
|
||||
## Order
|
||||
|
||||
The return can be associated with an [order](./orders.md). An order is represented by the `Order` entity.
|
||||
|
||||
You can access the order’s ID using the `order_id` property. You can also access the order by expanding the `order` relation and accessing `return.order`.
|
||||
|
||||
### Swap
|
||||
|
||||
The return can be associated with a [swap](./swaps.md). A swap is represented by the `Swap` entity.
|
||||
|
||||
You can access the swap’s ID using the `swap_id` property. You can also access the swap by expanding the `swap` relation and accessing `return.swap`.
|
||||
|
||||
### ClaimOrder
|
||||
|
||||
The return can be associated with a [claim](./claims.md). A claim is represented by the `ClaimOrder` entity.
|
||||
|
||||
You can access the claim’s ID using the `claim_order_id` property. You can also access the claim by expanding the `claim_order` relation and accessing `return.claim_order`.
|
||||
|
||||
### ShippingMethod
|
||||
|
||||
The return can be associated with a [shipping method](../carts-and-checkout/shipping.md#shipping-method). This would be the shipping method the customer chose when they created the swap. A shipping method is represented by the `ShippingMethod` entity.
|
||||
|
||||
You can access the shipping method by expanding the `shipping_method` relation and accessing `return.shipping_method`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage returns](./admin/manage-returns.mdx)
|
||||
- [Orders architecture overview](./orders.md)
|
||||
- [Swaps architecture overview](./swaps.md)
|
||||
- [Claims architecture overview](./claims.md)
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
description: "Learn how to implement a create return flow in the storefront."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create a Return in the Storefront
|
||||
|
||||
In this document, you’ll learn how to implement a create return flow in the storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Customers may need to return items they received from an order they placed for different reasons, such as ordering an incorrect size of the item.
|
||||
|
||||
The Medusa backend allows automating the process of returning an item by providing the necessary mechanism that allows customers to create the return request themselves. This guide illustrates how you can implement that mechanism in your storefront.
|
||||
|
||||
The process of creating a return is as follows:
|
||||
|
||||
- Ask the customer to select the items they want to return. You can also allow customers to select the return shipping option to use to return the item.
|
||||
- Create the return in the Medusa backend.
|
||||
|
||||
:::note
|
||||
|
||||
Refunding the customer is handled by admins. You can learn how to implement or use this functionality in the [Manage Returns guide](../admin/manage-returns.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Collecting Return Details
|
||||
|
||||
When a customer wants to create a return, they must choose the items they want to return. To display the items in the order, you can retrieve the order as explained in [this guide](./retrieve-order-details.mdx). You can then display the items in an order using `order.items`, which is an array of items.
|
||||
|
||||
### Showing Return Shipping Options
|
||||
|
||||
You can optionally allow customers to choose a return shipping option that they’ll use to return the items. To show the customers the available return shipping options, send a request to the Get [Shipping Options endpoint](https://docs.medusajs.com/api/store#shipping-options_getshippingoptions):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.shippingOptions.list({
|
||||
is_return: "true",
|
||||
})
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useShippingOptions } from "medusa-react"
|
||||
|
||||
const ReturnShippingOptions = () => {
|
||||
const {
|
||||
shipping_options,
|
||||
isLoading,
|
||||
} = useShippingOptions({
|
||||
is_return: "true",
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{shipping_options?.length &&
|
||||
shipping_options?.length > 0 && (
|
||||
<ul>
|
||||
{shipping_options?.map((shipping_option) => (
|
||||
<li key={shipping_option.id}>
|
||||
{shipping_option.id}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReturnShippingOptions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/shipping-options?is_return=true`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint allows you to pass the `is_return` query parameter to indicate whether the shipping options should be return shipping options. You can learn about other available filters in the [API reference](https://docs.medusajs.com/api/store#shipping-options_getshippingoptions).
|
||||
|
||||
The request returns an array of shipping option objects.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create Return
|
||||
|
||||
You can create the return by sending a request to the [Create Return endpoint](https://docs.medusajs.com/api/store#returns_postreturns):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.returns.create({
|
||||
order_id,
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping: {
|
||||
option_id,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data.return.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateReturn } from "medusa-react"
|
||||
|
||||
const CreateReturn = () => {
|
||||
const createReturn = useCreateReturn()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createReturn.mutate({
|
||||
order_id,
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping: {
|
||||
option_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateReturn
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/returns`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_id,
|
||||
items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping: {
|
||||
option_id,
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data.return.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `order_id`: a string indicating the ID of the order to create the return for.
|
||||
- `items`: an array of objects, each object being an item from the order to return. Each object must have the following properties:
|
||||
- `item_id`: a string indicating the ID of the item in the order.
|
||||
- `quantity`: a number indicating the quantity to return.
|
||||
|
||||
You can optionally pass the `return_shipping` parameter, which is the return shipping option that the customer will use to return the item. It’s an object that has a required property `option_id`, which is a string indicating the ID of the return shipping option.
|
||||
|
||||
The request returns the created return as an object.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage returns as an admin](../admin/manage-returns.mdx)
|
||||
- [How to implement a create swap flow in a storefront](./create-swap.mdx)
|
||||
@@ -0,0 +1,252 @@
|
||||
---
|
||||
description: "Learn how to implement a create swap flow in a storefront."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create a Swap in the Storefront
|
||||
|
||||
In this document, you’ll learn how to implement a create swap flow in a storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Swaps allow customers to exchange items they ordered with new ones. This can be helpful if the customer ordered and received an item they didn’t like, if they ordered an incorrect size, or something similar.
|
||||
|
||||
The Medusa backend allows automating the process of exchanging an item with another by providing the necessary mechanism that allows customers to create the swap request themselves. This guide illustrates how you can implement that mechanism in your storefront.
|
||||
|
||||
The process of creating a swap is as follows:
|
||||
|
||||
- Ask the customer to select the items they want to replace, and which items they want to replace them with. You can also allow customers to select the return shipping option to use to return the item.
|
||||
- Create the swap in the Medusa backend.
|
||||
- Show a checkout flow using the swap’s cart. This allows the customer to provide their shipping details and authorize payment in a flow similar to that of placing an order.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Collecting Swap Details
|
||||
|
||||
When a customer wants to create a swap, they must choose the items they want to return or replace and the items they want to receive instead.
|
||||
|
||||
To display the items in the order, you can retrieve the order as explained in [this guide](./retrieve-order-details.mdx). You can then display the items in an order using `order.items`, which is an array of items.
|
||||
|
||||
To allow the customers to choose other items to replace the items from the order, you can show them the available products in your store to choose from them. You can learn how to retrieve products in your storefront using [this guide](../../products/storefront/show-products.mdx).
|
||||
|
||||
You can optionally allow customers to choose a return shipping option that they’ll use to return the items. You can learn how to retrieve return shipping options in [this guide](./create-return.mdx#showing-return-shipping-options).
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create the Swap
|
||||
|
||||
After collecting the swap details in step 1, you can create a swap in the Medusa backend by sending a request to the [Create Swap endpoint](https://docs.medusajs.com/api/store#swaps_postswaps):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.swaps.create({
|
||||
order_id,
|
||||
return_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping_option,
|
||||
})
|
||||
.then(({ swap }) => {
|
||||
console.log(swap.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateSwap } from "medusa-react"
|
||||
|
||||
const CreateSwap = () => {
|
||||
const createSwap = useCreateSwap()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createSwap.mutate({
|
||||
order_id,
|
||||
return_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping_option,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateSwap
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/swaps`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_id,
|
||||
return_items: [
|
||||
{
|
||||
item_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
return_shipping_option,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ swap }) => {
|
||||
console.log(swap.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `order_id`: a string indicating the ID of the order that this swap is created for.
|
||||
- `return_items`: an array of objects, each object being the item to return. Each object should have the following properties:
|
||||
- `item_id`: a string indicating the ID of the item in the order.
|
||||
- `quantity`: a number indicating the quantity to return.
|
||||
- `additional_items`: an array of objects, each object being the new item to receive. Each object should have the following properties:
|
||||
- `variant_id`: a string indicating the ID of the product variant.
|
||||
- `quantity`: a number indicating the quantity to add.
|
||||
|
||||
You can optionally pass the `return_shipping_option` body parameter, which is a string indicating the ID of the shipping option.
|
||||
|
||||
The request returns the swap as an object.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Complete Swap with Checkout Flow
|
||||
|
||||
The swap can be completed in the same flow as a checkout flow. Since a swap is associated with a cart, you can implement the checkout flow using the cart of the swap. You can access the cart of a swap in the swap object using `swap.cart`.
|
||||
|
||||
Since the Medusa backend knows the cart is associated with the swap, it will ensure that the flow is performed in the context of a swap. You can learn how to implement a checkout flow in your storefront using [this guide](../../carts-and-checkout/storefront/implement-checkout-flow.mdx).
|
||||
|
||||
:::note
|
||||
|
||||
When you complete the cart, the returned `type` field can be used to indicate the context of the checkout flow. In the case of a swap, the value of `type` will be `swap`.
|
||||
|
||||
:::
|
||||
|
||||
### Retrieve Swap by Cart ID
|
||||
|
||||
During your checkout flow, you might need to retrieve the swap using the cart’s ID. For example, if you want to display the swap’s details after the cart is successfully completed. You can do that using the [Get by Cart ID endpoint](https://docs.medusajs.com/api/store#swaps_getswapsswapcartid):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.swaps.retrieveByCartId(cartId)
|
||||
.then(({ swap }) => {
|
||||
console.log(swap.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCartSwap } from "medusa-react"
|
||||
|
||||
const Swap = () => {
|
||||
const {
|
||||
swap,
|
||||
isLoading,
|
||||
} = useCartSwap(cartId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{swap && <span>{swap.id}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Swap
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/swaps/${cartId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ swap }) => {
|
||||
console.log(swap.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the cart as a path parameter.
|
||||
|
||||
The request returns the swap as an object.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to implement a create return flow in a storefront](./create-return.mdx)
|
||||
- [How to retrieve order details in a storefront](./retrieve-order-details.mdx)
|
||||
@@ -0,0 +1,477 @@
|
||||
---
|
||||
description: 'Learn how to implement order-edit related features in the storefront using REST APIs. This includes showing the customer order-edit requests, authorizing additional payments, and confirming or declining order edits.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Handle an Order Edit in Storefront
|
||||
|
||||
In this document, you’ll learn how to allow a customer to confirm or decline an Order Edit.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
A merchant can request to edit an order to make changes to its items. The change can include removing an item, adding a new item, and changing the quantity of an item in the original order.
|
||||
|
||||
When the Order Edit is in the “request” state, it requires either a confirmation from the customer, or it can be force-confirmed by the merchant.
|
||||
|
||||
This guide focuses on how to use the Storefront APIs to implement the flow that allows a customer to either confirm or decline an Order Edit.
|
||||
|
||||
:::note
|
||||
|
||||
You can check out how to implement order editing using the Admin APIs in [this documentation](../admin/edit-order.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### Scenarios
|
||||
|
||||
You want to implement the following functionalities in your storefront:
|
||||
|
||||
- List and show customers order-edit requests.
|
||||
- Confirm order edits and authorize any additional payment if necessary.
|
||||
- Decline order edits.
|
||||
|
||||
:::note
|
||||
|
||||
You can perform other functionalities related to order editing. To learn more, check out the API reference.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
You must have an existing order edit in the “request” state.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve an Order Edit
|
||||
|
||||
You can retrieve a single order edit by its ID by sending a request to the [Get Order Edit](https://docs.medusajs.com/api/store#order-edits_getordereditsorderedit) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orderEdits.retrieve(orderEditId)
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
// show changed items to the customer
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useOrderEdit } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const { order_edit, isLoading } = useOrderEdit(orderEditId)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/order-edits/${orderEditId}`)
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.changes)
|
||||
// show changed items to the customer
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request requires the order edit’s ID as a path parameter.
|
||||
|
||||
It returns the Order Edit as an object. Some of its important fields are:
|
||||
|
||||
- `order_id`: The ID of the order that this Order Edit belongs to.
|
||||
- `difference_due`: The amount to either be refunded or paid. If the amount is greater than 0, then the customer is required to pay an additional amount. If the amount is less than 0, then the merchant has to refund the difference to the customer.
|
||||
- `payment_collection_id`: The ID of the payment collection. This will be used to authorize additional payment if necessary.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about what fields to expect in the [API reference](https://docs.medusajs.com/api/store#order-edits_getordereditsorderedit).
|
||||
|
||||
:::
|
||||
|
||||
### Show Changed Items
|
||||
|
||||
All data about changes to the original order’s items can be found in `order_edit.changes`. `changes` is an array of item changes. Each item change includes the following fields:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```ts
|
||||
{
|
||||
type: string,
|
||||
line_item: LineItem | null,
|
||||
original_line_item: LineItem | null
|
||||
}
|
||||
```
|
||||
|
||||
`type` can be either:
|
||||
|
||||
- `item_add`: In this case, a new item is being added. `line_item` will be an item object and `original_line_item` will be `null`.
|
||||
- `item_update`: In this case, an item’s quantity in the original order is updated. `line_item` will be the updated item, and `original_line_item` will be the item in the original order. You can either just use `line_item` to show the new quantity, or show the customer a comparison between the old and new quantity using `original_line_item` as well.
|
||||
- `item_remove`: In this case, an item in the original order is removed. The `original_line_item` will be the item in the original order, and `line_item` will be `null`.
|
||||
|
||||
Here’s an example of how you can use this data to show the customer the requested edits to the order:
|
||||
|
||||
```tsx
|
||||
<ul>
|
||||
{orderEdit.changes.map((itemChange) => (
|
||||
<li key={itemChange.id}>
|
||||
<strong>
|
||||
{
|
||||
itemChange.line_item ?
|
||||
itemChange.line_item.title :
|
||||
itemChange.original_line_item.title
|
||||
}
|
||||
</strong>
|
||||
{itemChange.type === "added" && <span>New Item</span>}
|
||||
{itemChange.type === "removed" && (
|
||||
<span>Removed Item</span>
|
||||
)}
|
||||
{itemChange.type === "edited" &&
|
||||
<span>
|
||||
Edited Item
|
||||
Old Quantity: {itemChange.original_line_item.quantity}
|
||||
New Quantity: {itemChange.line_item.quantity}
|
||||
</span>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handle Payment
|
||||
|
||||
After viewing the changes in the order edit, the customer can choose to confirm or decline the order edit.
|
||||
|
||||
In case the customer wants to confirm the order edit, you must check whether a refund or an additional payment is required. You can check that by checking the value of `difference_due`.
|
||||
|
||||
### Refund Amount
|
||||
|
||||
If `difference_due` is less than 0, then the amount will be refunded to the customer by the merchant from the Medusa admin. No additional actions are required here before [completing the order edit](#complete-the-order-edit).
|
||||
|
||||
### Make Additional Payments
|
||||
|
||||
:::note
|
||||
|
||||
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](https://docs.medusajs.com/api/store#payment-collections_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 processors. These can be retrieved from the details of [the region of the order](https://docs.medusajs.com/api/store#regions_getregions).
|
||||
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](https://docs.medusajs.com/api/store#payment-collections_postpaymentcollectionspaymentcollectionsessionsbatch) 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" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
medusa.paymentCollections.managePaymentSession(paymentCollectionId, {
|
||||
provider_id,
|
||||
})
|
||||
.then(({ payment_collection }) => {
|
||||
console.log(payment_collection.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useManagePaymentSession } from "medusa-react"
|
||||
|
||||
const OrderEditPayment = () => {
|
||||
const managePaymentSession = useManagePaymentSession(
|
||||
paymentCollectionId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAdditionalPayment = (provider_id: string) => {
|
||||
managePaymentSession.mutate({
|
||||
provider_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEditPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/store/payment-collections/${paymentCollectionId}/sessions`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider_id,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ payment_collection }) => {
|
||||
console.log(payment_collection.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</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 processor. The [Authorize Payment Session](https://docs.medusajs.com/api/store#payment-collections_postpaymentcollectionssessionssessionauthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa
|
||||
.paymentCollection
|
||||
.authorizePaymentSession(
|
||||
paymentCollectionId,
|
||||
paymentSessionId
|
||||
)
|
||||
.then(({ payment_session }) => {
|
||||
console.log(payment_session.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAuthorizePaymentSession } from "medusa-react"
|
||||
|
||||
const OrderEditPayment = () => {
|
||||
const authorizePaymentSession = useAuthorizePaymentSession(
|
||||
paymentCollectionId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAuthorizePayment = (paymentSessionId: string) => {
|
||||
authorizePaymentSession.mutate(paymentSessionId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEditPayment
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/store/payment-collection/${paymentCollectionId}` +
|
||||
`/sessions/${paymentSessionId}/authorize`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ payment_session }) => {
|
||||
console.log(payment_session.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
After performing the above steps, you can [complete the Order Edit](#complete-the-order-edit).
|
||||
|
||||
---
|
||||
|
||||
## Complete the Order Edit
|
||||
|
||||
To confirm and complete the order edit, send a request to the [Complete Order Edit](https://docs.medusajs.com/api/store#order-edits_postordereditsordereditcomplete) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orderEdits.complete(orderEditId)
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.confirmed_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCompleteOrderEdit } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const completeOrderEdit = useCompleteOrderEdit(orderEditId)
|
||||
// ...
|
||||
|
||||
const handleCompleteOrderEdit = () => {
|
||||
completeOrderEdit.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/order-edits/${orderEditId}/complete`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.confirmed_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the order edit’s ID as a path parameter.
|
||||
|
||||
It returns the full Order Edit object. You can find properties related to the order confirmation, such as `order_edit.confirmed_at`.
|
||||
|
||||
After completing the order edit, the changes proposed in the Order Edit are reflected in the original order.
|
||||
|
||||
:::info
|
||||
|
||||
If the payment isn’t authorized first, the order edit completion will fail.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Decline an Order Edit
|
||||
|
||||
If the customer wants to decline the Order Edit, you can do that by sending a request to the Decline Order Edit endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orderEdits.decline(orderEditId, {
|
||||
decline_reason: "I am not satisfied",
|
||||
})
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.declined_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useDeclineOrderEdit } from "medusa-react"
|
||||
|
||||
const OrderEdit = () => {
|
||||
const declineOrderEdit = useDeclineOrderEdit(orderEditId)
|
||||
// ...
|
||||
|
||||
const handleDeclineOrderEdit = () => {
|
||||
declineOrderEdit.mutate({
|
||||
declined_reason: "I am not satisfied",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderEdit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/store/order-edits/${orderEditId}/decline`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
decline_reason: "I am not satisfied",
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ order_edit }) => {
|
||||
console.log(order_edit.declined_at)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request requires passing the order edit’s ID as a path parameter.
|
||||
|
||||
In the request body parameters, you can optionally pass the `decline_reason` parameter. It’s a string that indicates to the merchant the reason the customer declined the order edit.
|
||||
|
||||
If the Order Edit is declined, the changes requested in the Order Edit aren't reflected on the original order and no refund or additional payments are required.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Edit an order using Admin APIs](../admin/edit-order.mdx)
|
||||
@@ -0,0 +1,210 @@
|
||||
---
|
||||
description: 'Learn how to implement the order-claim flow in the storefront. This includes allowing customers to claim their orders, and verify a claim to an order.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Implement Claim Order Flow in Storefront
|
||||
|
||||
In this document, you’ll learn how to implement the claim order flow in a storefront to allow customers to claim their orders.
|
||||
|
||||
:::note
|
||||
|
||||
This flow was added starting from Medusa v1.7. You can learn more about upgrading in the [upgrade guide](../../../upgrade-guides/medusa-core/1-7-0.md).
|
||||
|
||||
:::
|
||||
|
||||
## Flow Overview
|
||||
|
||||
When a guest customer places an order, their order is not associated with any customer. The order is only associated with an email that the guest customer provides during checkout.
|
||||
|
||||
This email must be an email that isn’t used with an existing account. It can, however, be used to create another order as a guest customer.
|
||||
|
||||
After this customer registers with a different email and logs in, they can claim their order by providing the order’s ID. An email will then be sent to the email address associated with the order.
|
||||
|
||||
The email should contain a link to a page in the storefront, and the link should have a token as a parameter. This token will be used for verification.
|
||||
|
||||
The customer must then click the link in the email they received. If the token is valid, the order will be associated with the customer.
|
||||
|
||||

|
||||
|
||||
### What You’ll Learn
|
||||
|
||||
In this document, you’ll learn how to implement two parts of this flow:
|
||||
|
||||
1. Allow customers to claim their orders.
|
||||
2. Verify a claim to an order.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Handle Order Claim Request Event
|
||||
|
||||
When the customer requests to claim the order, an event will be triggered. You should subscribe to this event to send a confirmation email to the customer when the event is triggered.
|
||||
|
||||
You can learn how to implement this flow in [this documentation](../backend/handle-order-claim-event.md).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
It is assumed you already have an order placed by a guest customer. You can refer to the [Cart](../../carts-and-checkout/storefront/implement-cart) and [Checkout](../../carts-and-checkout/storefront/implement-checkout-flow.mdx) implementation documentation to learn how to implement them in your storefront.
|
||||
|
||||
In addition, it is assumed you already have a logged-in customer before performing the steps in this document. You can refer to the [API reference](https://docs.medusajs.com/api/store#auth_postauth) for more details on that.
|
||||
|
||||
---
|
||||
|
||||
## Request to Claim an Order
|
||||
|
||||
When the customer wants to claim an order, they must supply its ID.
|
||||
|
||||
To allow the customer to claim an order, send a request to the Claim an Order endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orders.requestCustomerOrders({
|
||||
order_ids: [
|
||||
order_id,
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// an error occurred
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/orders/batch/customer/token`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
order_ids: [
|
||||
order_id,
|
||||
],
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// display an error to the customer
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as a body parameter the array `order_ids`. Each item in the array is the ID of an order that the customer wants to claim. You can pass more than one ID.
|
||||
|
||||
If the customer’s request has been processed successfully, the request returns a response with a `200` status code.
|
||||
|
||||
The customer at this point will receive an email with a link to verify their claim on the order.
|
||||
|
||||
---
|
||||
|
||||
## Manually Verify a Claim to an Order
|
||||
|
||||
The link in the email that the customer receives should be a page in your storefront that accepts a `token` query parameter.
|
||||
|
||||
Then, you send a request to the Verify Claim Order endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orders.confirmRequest({
|
||||
token,
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// an error occurred
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useGrantOrderAccess } from "medusa-react"
|
||||
|
||||
const ClaimOrder = () => {
|
||||
const grantOrderAccess = useGrantOrderAccess()
|
||||
// ...
|
||||
|
||||
const handleVerifyOrderClaim = (token: string) => {
|
||||
grantOrderAccess.mutate(({
|
||||
token,
|
||||
}))
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ClaimOrder
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/orders/customer/confirm`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
.catch(() => {
|
||||
// display an error to the customer
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts as a body parameter the string `token`. This would be the token passed as a parameter to your storefront page through the link in the email.
|
||||
|
||||
If the verification is successful, the order will now be associated with the customer and the customer will be able to see it among their orders.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Send a confirmation email to claim an order](../backend/handle-order-claim-event.md)
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
description: "Learn the different ways you can retrieve and view a customer’s orders, whether they're a guest customer or logged-in."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Retrieve Order Details on the Storefront
|
||||
|
||||
In this document, you’ll learn the different ways you can retrieve and view a customer’s orders, whether they're a guest customer or logged-in.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Order by ID
|
||||
|
||||
Retrieving an order by its ID is useful for different scenarios, such as using an order details page. You can use this method for both logged in and guest customers.
|
||||
|
||||
You can retrieve an order by its ID using the [Get Order endpoint](https://docs.medusajs.com/api/store#orders_getordersorder):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orders.retrieve(orderId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useOrder } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useOrder(orderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && <span>{order.display_id}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/orders/${orderId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the order’s ID to be passed as a path parameter. You can utilize the [expand](https://docs.medusajs.com/api/store#expanding-fields) and [fields](https://docs.medusajs.com/api/store#selecting-fields) query parameters to select parameters and relations to return.
|
||||
|
||||
The request returns the order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Order by Display ID
|
||||
|
||||
Display IDs allow you to show human-readable IDs to your customers. Retrieving an order by its display ID is useful in many situations, such as allowing customers to look up their orders with a search field. This method of retrieving an order can be used for both logged-in customers and guest customers.
|
||||
|
||||
You can retrieve an order by its display ID using the [Look Up Order endpoint](https://docs.medusajs.com/api/store#orders_getorders):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orders.lookupOrder({
|
||||
display_id: 1,
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useOrders } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useOrders(orderId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && <span>{order.display_id}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/orders?display_id=1&email=user@example.com`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires two query parameters:
|
||||
|
||||
- `display_id`: a string indicating display ID of the order. If you already have an order object, you can retrieve the display ID using `order.display_id`.
|
||||
- `email`: a string indicating the email associated with the order.
|
||||
|
||||
You can pass other query parameters to filter the orders even further, and the endpoint will return the first order that matches the filters. Learn more about available query parameters in the [API reference](https://docs.medusajs.com/api/store#orders_getorders).
|
||||
|
||||
The request returns the order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Order by Cart ID
|
||||
|
||||
In certain scenarios, you may need to retrieve an order’s details using the ID of the cart associated with the order. This can be useful when showing a success page after a cart is completed and an order is placed.
|
||||
|
||||
You can retrieve an order by the cart ID using the [Get by Cart ID endpoint](https://docs.medusajs.com/api/store#orders_getordersordercartid):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.orders.retrieveByCartId(cartId)
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCartOrder } from "medusa-react"
|
||||
|
||||
const Order = () => {
|
||||
const {
|
||||
order,
|
||||
isLoading,
|
||||
} = useCartOrder(cartId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{order && <span>{order.display_id}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Order
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/orders/cart/${cartId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ order }) => {
|
||||
console.log(order.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the cart as a path parameter.
|
||||
|
||||
The request returns the order as an object.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Customer’s Orders
|
||||
|
||||
When a customer is logged in, you can retrieve a list of their orders. This is typically useful to show a customer their orders in their profile. This method can only be used for logged-in customers.
|
||||
|
||||
You can learn how to retrieve a customer’s orders in the [How to Implement Customer Profiles](/modules/customers/storefront/implement-customer-profiles#retrieve-a-customers-orders) documentation.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to handle order edits in the storefront](./handle-order-edits.mdx)
|
||||
- [How to implement claim order flow in the storefront](./implement-claim-order.mdx)
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
description: "Learn what swaps are, swap processes implemented in the Medusa backend, and a swap’s relation to other entities"
|
||||
---
|
||||
|
||||
# Swaps Architecture Overview
|
||||
|
||||
In this document, you’ll learn what swaps are, swap processes implemented in the Medusa backend, and a swap’s relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
After an order is created and fulfilled, the customer may need to replace an item they received with another. They can then create a swap, requesting to return an item they have in place for a new item. The swap, similar to an order, can then be paid, fulfilled, and more.
|
||||
|
||||
The Medusa core provides the necessary implementation and functionalities that allow you to integrate swaps in your store and automate the Return Merchandise Authorization (RMA) flow.
|
||||
|
||||
---
|
||||
|
||||
## Swap Entity Overview
|
||||
|
||||
Some of the attributes of the `Swap` entity include:
|
||||
|
||||
- `fulfillment_status`: a string indicating the status of the swap’s fulfillment. Its possible values indicated by the [SwapFulfillmentStatus enum](../../references/entities/enums/SwapFulfillmentStatus.md) can determine whether all items have been fulfilled, shipped, or canceled.
|
||||
- `payment_status`: a string indicating the status of the swap’s payment. Its possible values indicated by the [SwapPaymentStatus enum](../../references/entities/enums/SwapFulfillmentStatus.md) can determine whether the payment of the swap has been captured or refunded.
|
||||
- `difference_due`: An integer indicating the difference amount between the order’s original total and the new total imposed by the swap.
|
||||
- If the value of `difference_due` is negative, that means the customer should be refunded.
|
||||
- If it’s positive, that means the customer must authorize additional payment.
|
||||
- If it’s zero, then no refund or additional payment is required.
|
||||
- `confirmed_at`: A date indicating when the swap was confirmed by the customer.
|
||||
- `canceled_at`: A date indicating when the swap was canceled.
|
||||
- `no_notification`: a boolean value indicating whether the customer should receive notifications when the order is updated.
|
||||
- `allow_backorder`: a boolean value indicating whether a swap can be created and completed with items that are out of stock.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the full [Swap entity in the entities reference](../../references/entities/classes/Swap.md).
|
||||
|
||||
---
|
||||
|
||||
## How are Swaps Created
|
||||
|
||||
In Medusa, Swaps are created by the customer through the storefront. This ensures an automated Return Merchandise Authorization (RMA) flow. This section explains the ideal and recommended process, but you’re free in how you choose to implement Swaps creation flow.
|
||||
|
||||
### Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a swap. It is generated when the swap creation process is started and can be used to retry the swap creation safely if an error occurs. The idempotency key is stored in the `Swap` entity under the attribute `idempotency_key`.
|
||||
|
||||
Keep in mind that the idempotency key stored in the swap is only used when creating the swap. All operations related to the swap’s cart and its completion use the [cart’s idempotency key](../carts-and-checkout/cart.md#idempotency-key).
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
### Swap Creation Process
|
||||
|
||||
The customer starts by creating their swap, which can be done through the Create Swap endpoint. In this endpoint, the following steps are implemented:
|
||||
|
||||

|
||||
|
||||
1. The swap’s creation is initiated with the `SwapService`'s [create method](../../references/services/classes/SwapService.md#create):
|
||||
1. The order is first validated to see if a swap can be created. This includes checking the `payment_status` of the order to ensure the payment has already been captured, and the `fulfillment_status` of the order to ensure the status isn’t `not_fulfilled`. If any of these conditions aren’t met, the process is terminated with an error.
|
||||
2. The items that the customer chose to return are then validated. If any item has been previously canceled, meaning that its order, swap, or claim have been previously canceled, the process is terminated with an error.
|
||||
3. If all the conditions above are met, the swap is then created and associated with the order using the `order_id` attribute.
|
||||
4. A return is created and linked to the swap. The return’s items are set to the items that the customer chose to return.
|
||||
2. After the swap has been created, a cart is then created for the swap using the `SwapService`'s [createCart method](../../references/services/classes/SwapService.md#createcart). The cart is used to finalize the process of the swap creation process, but more on that later. The swap is associated with the cart using the `cart_id` attribute.
|
||||
3. The return associated with the swap is marked as fulfilled, as this is taken care of by the customer.
|
||||
|
||||
After the swap has been created, the customer should undergo a swap-completion process similar to the checkout process where they provide shipping and billing addresses, choose shipping and payment methods, authorize any additional payment required, then finally completing the cart.
|
||||
|
||||
This is all made possible since the swap is linked to a cart, so the same checkout flow can be implemented in a swap context. The cart completion strategy also handles the completion differently for swaps, as explained [here](../carts-and-checkout/cart.md#cart-completion).
|
||||
|
||||
---
|
||||
|
||||
## Handling Swap Payment
|
||||
|
||||
After the swap has been created and completed by the customer, the merchant can then process the payment of the swap. This includes either refunding or capturing an additional payment, which is decided by the value of the `difference_due` attribute as explained in the [Swap Entity section](#swap-entity-overview).
|
||||
|
||||
The processing of the swap’s payment can be done either using the `SwapService`'s [processDifference method](../../references/services/classes/SwapService.md#processdifference), or using the [Process Swap Payment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapprocesspayment).
|
||||
|
||||
If the swap requires any additional payment, the customer is expected to have authorized the payment during the [swap and cart completion flow explained earlier](#how-are-swaps-created). The merchant can then capture the payment and the `payment_status` of the swap is set to `captured`.
|
||||
|
||||
If the swap requires a refund, the merchant can refund the payment to the customer and the `payment_status` of the swap is set to `difference_refunded`. The same status is used if no refund or additional payment are required.
|
||||
|
||||
If the capturing or refunding of the payment causes any errors, the payment status is changed to `requires_action`.
|
||||
|
||||
A swap’s payment can be accessed by expanding the `payment` relation and accessing `swap.payment`. A payment is represented by the `Payment` entity.
|
||||
|
||||
---
|
||||
|
||||
## Handling Swap Fulfillment
|
||||
|
||||
After the swap has been created and completed by the customer, the merchant should handle the [fulfillment](./fulfillments.md) of the new items.
|
||||
|
||||
Although you have freedom in how you implement the process, the recommended process provided in the Medusa backend is as follows:
|
||||
|
||||

|
||||
|
||||
1. The fulfillment is created either using the `SwapService`'s [createFulfillment method](../../references/services/classes/SwapService.md#createfulfillment) or using the [Create a Swap Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapfulfillments). This would set the `fulfillment_status` of the swap either to `fulfilled` if all items have been fulfilled, or `partially_fulfilled` if only some items were fulfilled.
|
||||
2. The shipment can then be created using the `SwapService`'s [createShipment method](../../references/services/classes/SwapService.md#createshipment) or using the [Create Swap Shipment endpoint](https://docs.medusajs.com/api/admin#orders_postordersorderswapsswapshipments). This would set the `fulfillment_status` of the swap either to `shipped` if all items have been shipped, or `partially_shipped` if only some items were shipped.
|
||||
3. Alternatively, a fulfillment can be canceled after it has been created using the `SwapService`'s [cancelFulfillment method](../../references/services/classes/SwapService.md#cancelfulfillment) or using the [Cancel Swap’s Fulfillment endpoint](https://docs.medusajs.com/api/admin#orders_postordersswapfulfillmentscancel). This would set the `fulfillment_status` of the swap to `canceled`.
|
||||
|
||||
A swap’s fulfillments can be accessed by expanding the `fulfillments` relation and accessing `swap.fulfillments`. A fulfillment is represented by the `Fulfillment` entity.
|
||||
|
||||
---
|
||||
|
||||
## Handling a Swap's Return
|
||||
|
||||
A swap's return can be marked as received, which would adjust the inventory and change the status of the return. This process is explained within the [Returns documentation](./returns.md#mark-return-as-received-process).
|
||||
|
||||
---
|
||||
|
||||
## Canceling a Swap
|
||||
|
||||
A swap can be canceled by the merchant if necessary. It can be done either through the `SwapService`'s [cancel method](../../references/services/classes/SwapService.md#cancel) or the [Cancel Swap endpoint](https://docs.medusajs.com/api/admin#orders_postordersswapcancel).
|
||||
|
||||
A swap can’t be canceled if:
|
||||
|
||||
- The payment has been refunded (the `payment_status` is either `difference_refunded`, `partially_refunded`, or `refunded`).
|
||||
- Its fulfillment isn’t canceled. You’ll have to cancel the swap’s fulfillment as explained in the [Handling Swap Fulfillment section](#handling-swap-fulfillment).
|
||||
- Its return isn’t canceled. You’ll have to [cancel the return](./returns.md#cancel-a-return) associated with the swap first before canceling the swap.
|
||||
|
||||
---
|
||||
|
||||
## RMA Automation with Swaps
|
||||
|
||||
Giving the client control over the creation of the swap and handling the returns allows businesses to automate their RMA flow.
|
||||
|
||||
The client, typically the customer, creates the swap, authorizes any additional payment required, and handles returning the item. The admin, typically the merchant, can view the created swap, process the payment, and fulfill and ship the additional items.
|
||||
|
||||

|
||||
|
||||
This removes any friction and support required between the client and the admin.
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
This section includes relations that weren’t mentioned in other sections.
|
||||
|
||||
### Order
|
||||
|
||||
The swap must be associated with the [order](./orders.md) it’s created for. An order is represented by the `Order` entity.
|
||||
|
||||
You can access the order’s ID using the `order_id` attribute. You can also access the order by expanding the `order` relation and accessing `swap.order`.
|
||||
|
||||
### Cart
|
||||
|
||||
The swap must be associated with a [cart](../carts-and-checkout/cart.md). A cart is represented by the `Cart` entity.
|
||||
|
||||
You can access the cart’s ID using the `cart_id` attribute. You can also access the cart by expanding the `cart` relation and accessing `swap.cart`.
|
||||
|
||||
### LineItem
|
||||
|
||||
The swap is associated with new items that should replace the customer’s items. These items are represented by the `LineItem` entity.
|
||||
|
||||
You can access the swap’s new items by expanding the `additional_items` relation and accessing `swap.additional_items`.
|
||||
|
||||
### Return
|
||||
|
||||
The swap is associated with a [return](./returns.md) that is created when the swap is created. A return is represented by the `Return` entity.
|
||||
|
||||
You can access the swap’s return by expanding the `return_order` relation and accessing `swap.return_order`.
|
||||
|
||||
### Address
|
||||
|
||||
The swap can be associated with shipping and billing addresses. Both shipping and billing addresses are represented by the `Address` entity.
|
||||
|
||||
You can access the swap’s billing address ID using the `billing_address_id` property. You can also access the billing address by expanding the `billing_address` relation and accessing `swap.billing_address`.
|
||||
|
||||
You can access the swap’s shipping address ID using the `shipping_address_id` property. You can also access the shipping address by expanding the `shipping_address` relation and accessing `swap.shipping_address`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage swaps](./admin/manage-swaps.mdx)
|
||||
- [Order Architecture Overview](./orders.md)
|
||||
@@ -0,0 +1,268 @@
|
||||
---
|
||||
description: 'Learn about the ecommerce features that Medusa provides including order management, product management, multi region and currency support, and more.'
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList'
|
||||
import Icons from '@theme/Icon'
|
||||
import LargeCardList from '@site/src/components/LargeCardList'
|
||||
import LargeCard from '@site/src/components/LargeCard'
|
||||
|
||||
# Commerce Modules
|
||||
|
||||
Medusa offers ecommerce features essential to all types of businesses as Commerce Modules. Starting from the basic products management and order management, to the more advanced automated return and exchange flows.
|
||||
|
||||
This part of the documentation covers Medusa’s ecommerce features and how they can be used. It also includes guides that help developers utilize these features to create unique digital experiences.
|
||||
|
||||
## Feature Highlights
|
||||
|
||||
This section gives an overview of the features available in Medusa. You can learn more about other available features by following the guides in this documentation.
|
||||
|
||||
### Optimized Shopping and Fulfillment Experience
|
||||
|
||||
Medusa provides the necessary features to build a customizable shopping experience for your customers. Medusa also offers features optimized for business operations to manage their orders efficiently.
|
||||
|
||||
<LargeCardList colSize={6}>
|
||||
<LargeCard
|
||||
Icon={Icons['check-circle-solid']}
|
||||
title='Orders'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/orders/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Process orders' payments and fulfillments with custom logic or third-party providers.
|
||||
- Create one or multiple fulfillments for items in the order.
|
||||
- Edit an order to add, update, or delete its items.
|
||||
- Handle and automate order returns, exchanges, and refunds.
|
||||
- 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']}
|
||||
title='Cart and Checkout'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/carts-and-checkout/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
<LargeCard
|
||||
Icon={Icons['users-solid']}
|
||||
title='Customers'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/customers/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Allow orders from both registered and unregistered customers.
|
||||
- Allow customers to create return or exchange requests from the storefront for better customer experience.
|
||||
- Assign customers different groups for segmentation and specify different pricing for customer groups.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
</LargeCardList>
|
||||
|
||||
### Advanced Product Configurations and Management
|
||||
|
||||
Medusa's products configuration allows managing products of different types including products with options and gift cards. Medusa also includes advanced features related to pricing and discounts.
|
||||
|
||||
<LargeCardList colSize={6}>
|
||||
<LargeCard
|
||||
Icon={Icons['tag-solid']}
|
||||
title='Products'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/products/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Create products with unlimited variants and options.
|
||||
- Organize products into nested categories of different hierarchies.
|
||||
- Associate products with collections, types, and tags.
|
||||
- Manage a product’s inventory, pricing, sales channels, and more.
|
||||
- Import and export products to and from Medusa using CSV files.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
<LargeCard
|
||||
Icon={Icons['gift-solid']}
|
||||
title='Gift Cards'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/gift-cards/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Offer your customers a gift card with unlimited denominations.
|
||||
- Set denomination prices per currency to cater for different regions.
|
||||
- Send custom gift cards to specific customers for marketing purposes.
|
||||
- Manage a custom gift card's balance amount.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
</LargeCardList>
|
||||
|
||||
|
||||
<LargeCard
|
||||
Icon={Icons['currency-dollar-solid']}
|
||||
title='Price Lists and Discounts'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/price-lists/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Create discounts and deals with advanced conditions and rules such as minimum cart quantity or specific products.
|
||||
- Offer free shipping, fixed discount, or percentage discount.
|
||||
- Override product prices using price lists and set special conditions such as specific customer groups.
|
||||
- Import prices into a price list from a CSV file.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
|
||||
### Ready Configurations for International Selling
|
||||
|
||||
Medusa's multi-region setup and sales channels allow businesses to sell internationally and sell across platforms. Medusa allows configuring regions differently to cater for different markets across the globe.
|
||||
|
||||
<LargeCardList colSize={6}>
|
||||
<LargeCard
|
||||
Icon={Icons['globe-europe-solid']}
|
||||
title='Multi-Region'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/regions-and-currencies/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Create an unlimited number of regions, each associated with a currency and at least one country.
|
||||
- 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.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
<LargeCard
|
||||
Icon={Icons['cash-solid']}
|
||||
title='Taxes'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/taxes/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Specify different tax providers for different regions.
|
||||
- Create tax providers using custom logic or third-party integrations.
|
||||
- Customize tax configurations using tax rates and tax overrides.
|
||||
- Enable tax-inclusive pricing for automatic tax-rate calculations during checkout.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
</LargeCardList>
|
||||
|
||||
<LargeCard
|
||||
Icon={Icons['channels-solid']}
|
||||
title='Sales Channels'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/sales-channels/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Create sales channels for your different platforms such as web, mobile, or marketplaces.
|
||||
- Specify the availability of products for each sales channel.
|
||||
- Associate orders with each sales channel for better handling and logistics.
|
||||
- Associate API keys with sales channels for easier development.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
|
||||
## Get Additional Help
|
||||
|
||||
If you have any questions about Medusa, its features, and development with it, feel free to reach out to the core team and the community behind Medusa on [Discord](https://discord.gg/medusajs).
|
||||
|
||||
## Popular Guides
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/storefront/implement-customer-profiles',
|
||||
label: 'Implement Accounts',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Add customer profiles to the storefront.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/storefront/implement-cart',
|
||||
label: 'Implement Cart',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Add cart functionality to the storefront.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Create a Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['credit-card-solid'],
|
||||
description: 'Learn how to create a payment processor.',
|
||||
},
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/admin/edit-order',
|
||||
label: 'Edit an Order',
|
||||
customProps: {
|
||||
icon: Icons['pencil-square-solid'],
|
||||
description: 'Edit an order with the Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/admin/import-prices',
|
||||
label: 'Import Prices',
|
||||
customProps: {
|
||||
icon: Icons['currency-dollar-solid'],
|
||||
description: 'Import prices to Medusa.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/admin/manage',
|
||||
label: 'Manage Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['channels-solid'],
|
||||
description: 'Manage sales channels.',
|
||||
}
|
||||
}
|
||||
]} />
|
||||
@@ -0,0 +1,423 @@
|
||||
---
|
||||
description: 'Learn how to bulk-import prices into Medusa using the Admin REST APIs. The steps include uploading a CSV file, creating a batch job for the import, and confirming the batch job.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Bulk Import Prices
|
||||
|
||||
In this document, you’ll learn how to bulk import prices into a price list using the Admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using Medusa’s [Batch Job Admin APIs](https://docs.medusajs.com/api/admin#batch-jobs), you can import prices into a price list.
|
||||
|
||||
:::caution
|
||||
|
||||
Importing prices into a price list removes all existing prices in the price list and adds the imported prices.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### File Service Plugin
|
||||
|
||||
Part of the process of importing prices is uploading a CSV file. This requires a file service plugin to be installed on your backend. If you don’t have any installed, you can install one of the following options:
|
||||
|
||||
- [MinIO](../../../plugins/file-service/minio.md) (At least version `1.1.0`)
|
||||
- [Spaces](../../../plugins/file-service/spaces.md)
|
||||
|
||||
### CSV File
|
||||
|
||||
You must have a CSV file that you will use to import prices into your Medusa backend. You can check [this CSV example file](https://medusa-doc-files.s3.amazonaws.com/price-list-import-template.csv) to see which format is required for your import.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
### Created Price List
|
||||
|
||||
Before importing the prices, you must have a price list to import them to.
|
||||
|
||||
You can use the [Create Price List](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelist) endpoint, or follow the [how-to guide to learn how to create and manage price lists using the Admin API](./manage-price-lists.mdx).
|
||||
|
||||
---
|
||||
|
||||
## 1. Upload CSV File
|
||||
|
||||
The first step is to upload the CSV file to import prices from.
|
||||
|
||||
You can do that by sending the following request to the [Upload Files](https://docs.medusajs.com/api/admin#uploads_postuploads) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.uploads.create(file) // file is an instance of File
|
||||
.then(({ uploads }) => {
|
||||
const key = uploads[0].key
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUploadFile } from "medusa-react"
|
||||
|
||||
const ImportPrices = () => {
|
||||
const uploadFile = useAdminUploadFile()
|
||||
// ...
|
||||
|
||||
const handleFileUpload = (file: File) => {
|
||||
uploadFile.mutate(file)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportPrices
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
const formData = new FormData()
|
||||
formData.append("files", file) // file is an instance of File
|
||||
|
||||
fetch(`<BACKEND_URL>/admin/uploads`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ uploads }) => {
|
||||
const key = uploads[0].key
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/uploads' \
|
||||
-H 'Authorization: Bearer {api_token}' \
|
||||
-H 'Content-Type: text/csv' \
|
||||
-F 'files=@"<FILE_PATH_1>"'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of uploads. Each item in the array is an object that includes the properties `url` and `key`. You’ll need the `key` to import the prices next.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create a Batch Job for Prices Import
|
||||
|
||||
To start a new price import, you must create a batch job.
|
||||
|
||||
You can do that by sending the following request to the [Create a Batch Job](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.create({
|
||||
type: "price-list-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
price_list_id,
|
||||
},
|
||||
dry_run: true,
|
||||
})
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateBatchJob } from "medusa-react"
|
||||
|
||||
const ImportPrices = () => {
|
||||
const createBatchJob = useAdminCreateBatchJob()
|
||||
// ...
|
||||
|
||||
const handleCreateBatchJob = () => {
|
||||
createBatchJob.mutate({
|
||||
type: "price-list-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
},
|
||||
dry_run: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportPrices
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "price-list-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
price_list_id,
|
||||
},
|
||||
dry_run: true,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs' \
|
||||
-H 'Authorization: Bearer {api_token}' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"type": "price-list-import",
|
||||
"context": {
|
||||
"fileKey": "<KEY>",
|
||||
"price_list_id": "<PRICE_LIST_ID>"
|
||||
},
|
||||
"dry_run": true
|
||||
}'
|
||||
# <KEY> is the key you obtained from the previous step
|
||||
# <PRICE_LIST_ID> is the ID of the price list to import prices into
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In the body of the request, you must pass the following parameters:
|
||||
|
||||
- `type`: Batch jobs can be of different types. For price imports, the type should always be `price-list-import`.
|
||||
- `context`: An object that must contain:
|
||||
- The `fileKey` property. The value of this property is the key received when you uploaded the CSV file.
|
||||
- The `price_list_id` property. The value of this property is the ID of the price list you’re importing the prices into.
|
||||
- `dry_run`: This is optional to include. If not set or if its value is `false`, the price import will start right after you send this request. Settings its value to `true` allows you to retrieve afterward a brief of the number of prices that will be added.
|
||||
|
||||
This request returns the batch job with its details such as the `status` or `id`.
|
||||
|
||||
:::note
|
||||
|
||||
If you don’t set `dry_run` or you set it to `false`, you don’t need to follow the rest of these steps.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Retrieve Batch Job
|
||||
|
||||
After creating the batch job, it will be pre-processed. At this point, the CSV file will be validated, and the number of prices to add are counted.
|
||||
|
||||
You can retrieve all the details of the batch job, including its status and the brief statistics related to the prices by sending the following request:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.retrieve(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminBatchJob } from "medusa-react"
|
||||
|
||||
const ImportPrices = () => {
|
||||
const { batch_job, isLoading } = useAdminBatchJob(batchJobId)
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* ... */}
|
||||
{isLoading && <span>Loading</span>}
|
||||
{batch_job && (
|
||||
<span>
|
||||
Status: {batch_job.status}.
|
||||
Number of Prices: {batch_job.result.count}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportPrices
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>' \
|
||||
-H 'Authorization: Bearer {api_token}'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the batch job’s ID as a parameter, which can be retrieved from the previous request. It returns the batch job in the response.
|
||||
|
||||
If the batch job has been pre-processed, the status of the batch job will be `pre_processed` and the `result` property will contain details about the import.
|
||||
|
||||
Here’s an example of the `result` property:
|
||||
|
||||
```json noReport
|
||||
"result": {
|
||||
"count": 5, // Total number of prices to be added
|
||||
"stat_descriptors": [ //details about the prices to be added
|
||||
{
|
||||
"key": "price-list-import-count",
|
||||
"name": "PriceList to import",
|
||||
"message": "5 prices will be added"
|
||||
}
|
||||
],
|
||||
"advancement_count": 0 //number of prices processed so far.
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Confirm Batch Job
|
||||
|
||||
A batch job can be confirmed only once the batch job has the status `pre_processed`. Once you confirm a batch job, the price import will start which will add prices to the price list
|
||||
|
||||
To confirm a batch job send the following request:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.confirm(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminConfirmBatchJob } from "medusa-react"
|
||||
|
||||
const ImportPrices = () => {
|
||||
const confirmBatchJob = useAdminConfirmBatchJob(batchJobId)
|
||||
// ...
|
||||
|
||||
const handleConfirmJob = () => {
|
||||
confirmBatchJob.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportPrices
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}/confirm`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>/confirm' \
|
||||
-H 'Authorization: Bearer {api_token}'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the batch job as a path parameter and returns the updated batch job. The returned batch job should have the status `confirmed`, which indicates that the batch job will now start processing.
|
||||
|
||||
### Checking the Status After Confirmation
|
||||
|
||||
After confirming the batch job, you can check the status while it is processing at any given point by retrieving the batch job. Based on the status, you can infer the progress of the batch job:
|
||||
|
||||
- If the status is `processing`, it means that the import is currently in progress. You can also check `result.advancement_count` to find out how many prices have been added so far.
|
||||
- If the status is `failed`, it means an error has occurred during the import. You can check the error in `result.errors`.
|
||||
- If the status is `completed`, it means the import has finished successfully.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Batch Jobs Overview](../../../development/batch-jobs/index.mdx)
|
||||
- [Batch Jobs API Reference](https://docs.medusajs.com/api/admin#batch-jobs)
|
||||
@@ -0,0 +1,699 @@
|
||||
---
|
||||
description: 'Learn how to implement price list functionalities for admins using the REST APIs. This includes how to create a price list, retrieving price list details, managing prices in the price list, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Price Lists
|
||||
|
||||
In this document, you’ll learn how to use the PriceList Admin APIs to create, update, and manage prices in a price list.
|
||||
|
||||
:::note
|
||||
|
||||
This document doesn't cover what price lists are and their basics. If you’re interested to learn about that, check out [this documentation](../price-lists.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, JavaScript’s Fetch API, or cURL.
|
||||
|
||||
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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Create Price List
|
||||
|
||||
When you create a price list, you can specify different conditions to control when the price list can be applied.
|
||||
|
||||
In the body of your request, aside from the required fields, you can send the following fields to apply different conditions:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js noReport
|
||||
{
|
||||
prices: [
|
||||
{
|
||||
region_id,
|
||||
currency_code,
|
||||
min_quantity,
|
||||
max_quantity
|
||||
}
|
||||
],
|
||||
customer_groups: [
|
||||
{
|
||||
id
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about what the conditions you can apply on a price list and its prices in this documentation.
|
||||
|
||||
:::
|
||||
|
||||
For example, sending the following request creates a price list with two prices: one that is applied when the maximum quantity of the product variant in the cart is 3; another is applied when the minimum quantity of the same variant in the cart is 4:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
import {
|
||||
PriceListStatus,
|
||||
PriceListType,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
medusa.admin.priceLists.create({
|
||||
name: "New Price List",
|
||||
description: "A new price list",
|
||||
type: PriceListType.SALE,
|
||||
status: PriceListStatus.ACTIVE,
|
||||
prices: [
|
||||
{
|
||||
amount: 1000,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
max_quantity: 3,
|
||||
},
|
||||
{
|
||||
amount: 1500,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
min_quantity: 4,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
PriceListStatus,
|
||||
PriceListType,
|
||||
} from "@medusajs/medusa"
|
||||
import { useAdminCreatePriceList } from "medusa-react"
|
||||
|
||||
const CreatePriceList = () => {
|
||||
const createPriceList = useAdminCreatePriceList()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createPriceList.mutate({
|
||||
name: "New Price List",
|
||||
description: "A new price list",
|
||||
type: PriceListType.SALE,
|
||||
status: PriceListStatus.ACTIVE,
|
||||
prices: [
|
||||
{
|
||||
amount: 1000,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
max_quantity: 3,
|
||||
},
|
||||
{
|
||||
amount: 1500,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
min_quantity: 4,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreatePriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URL>/admin/price-lists`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "New Price List",
|
||||
description: "A new price list",
|
||||
type: "sale",
|
||||
status: "active",
|
||||
prices: [
|
||||
{
|
||||
amount: 1000,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
max_quantity: 3,
|
||||
},
|
||||
{
|
||||
amount: 1500,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
min_quantity: 4,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<YOUR_BACKEND_URL>/admin/price-lists' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "New Price List",
|
||||
"description": "A new price list",
|
||||
"type": "sale",
|
||||
"status": "active",
|
||||
"prices": [
|
||||
{
|
||||
"amount": 1500,
|
||||
"variant_id": "<VARIANT_ID>",
|
||||
"max_quantity": 3,
|
||||
"currency_code": "eur"
|
||||
},
|
||||
{
|
||||
"amount": 1000,
|
||||
"variant_id": "<VARIANT_ID>",
|
||||
"currency_code": "eur",
|
||||
"min_quantity": 4
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the created price list.
|
||||
|
||||
You can check the full list of request body parameters in the [API reference](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelist).
|
||||
|
||||
---
|
||||
|
||||
## Get Price List’s Details
|
||||
|
||||
You can retrieve all of a price list’s details using the Get a Price List endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.admin.priceLists.retrieve(priceListId)
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { useAdminPriceList } from "medusa-react"
|
||||
|
||||
const PriceList = () => {
|
||||
const {
|
||||
price_list,
|
||||
isLoading,
|
||||
} = useAdminPriceList(priceListId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{price_list && <span>{price_list.name}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URL>/admin/price-lists/${priceListId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/price-lists/{id}' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Price List
|
||||
|
||||
After creating a price list, you can update all of its fields including its status, prices, and more using the [Update Price List](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelistpricelist) endpoint.
|
||||
|
||||
For example, by sending the following request the end date of the price list will be updated:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.admin.priceLists.update(priceListId, {
|
||||
ends_at: "2022-10-11",
|
||||
})
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
PriceListStatus,
|
||||
PriceListType,
|
||||
} from "@medusajs/medusa"
|
||||
import { useAdminUpdatePriceList } from "medusa-react"
|
||||
|
||||
const CreatePriceList = () => {
|
||||
const updatePriceList = useAdminUpdatePriceList(priceListId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updatePriceList.mutate({
|
||||
ends_at: "2022-10-11",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreatePriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URL>/admin/price-lists/${priceListId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ends_at: "2022-10-11",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/price-lists/<PRICE_LIST_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"ends_at": "2022-10-11"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the updated price list.
|
||||
|
||||
For a full list of request body parameters, check out the [API reference](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelistpricelist).
|
||||
|
||||
---
|
||||
|
||||
## Manage Prices
|
||||
|
||||
### Add Prices
|
||||
|
||||
You can add prices to your price list after creating it using the [Update Prices](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelistpricesbatch) endpoint.
|
||||
|
||||
You can also set the `override` request body parameter to `true` to replace the existing prices in the price list.
|
||||
|
||||
For example, sending the following request adds a new price to the price list:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.admin.priceLists.addPrices(priceListId, {
|
||||
prices: [
|
||||
{
|
||||
amount: 1200,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreatePriceListPrices } from "medusa-react"
|
||||
|
||||
const PriceList = () => {
|
||||
const addPrice = useAdminCreatePriceListPrices(priceListId)
|
||||
// ...
|
||||
|
||||
const handleAddPrice = () => {
|
||||
addPrice.mutate({
|
||||
prices: [
|
||||
{
|
||||
amount: 1200,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URL>/admin/price-lists/${priceListId}/prices/batch`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prices: [
|
||||
{
|
||||
amount: 1200,
|
||||
variant_id,
|
||||
currency_code: "eur",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ price_list }) => {
|
||||
console.log(price_list.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/price-lists/<PRICE_LIST_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"prices": [
|
||||
{
|
||||
"amount": 1200,
|
||||
"variant_id": "<VARIANT_ID>",
|
||||
"currency_code": "eur"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the price list with the updated prices.
|
||||
|
||||
For a full list of request body parameters, check out the [API reference](https://docs.medusajs.com/api/admin#price-lists_postpricelistspricelistpricesbatch).
|
||||
|
||||
### Delete a Product’s Prices
|
||||
|
||||
You can delete all the prices of a product’s variants using the [Delete Product Prices](https://docs.medusajs.com/api/admin#price-lists_deletepricelistspricelistpricesbatch) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```tsx
|
||||
medusa
|
||||
.admin
|
||||
.priceLists
|
||||
.deleteProductPrices(priceListId, productId)
|
||||
.then(({ ids, object, deleted }) => {
|
||||
console.log(ids.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminDeletePriceListProductPrices,
|
||||
} from "medusa-react"
|
||||
|
||||
const PriceList = () => {
|
||||
const deletePrices = useAdminDeletePriceListProductPrices(
|
||||
priceListId,
|
||||
productId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDeletePrices = () => {
|
||||
deletePrices.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```jsx
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/price-lists/${priceListId}/products/${productId}/prices`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ ids, object, deleted }) => {
|
||||
console.log(ids.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/price-lists/<PRICE_LIST_ID>/products/<PRODUCT_ID>/prices' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the IDs of the deleted prices.
|
||||
|
||||
### Delete a Variant’s Prices
|
||||
|
||||
You can delete all the prices of a variant using the [Delete Variant Prices](https://docs.medusajs.com/api/admin#price-lists_deletepricelistspricelistvariantsvariantprices) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```tsx
|
||||
medusa
|
||||
.admin
|
||||
.priceLists
|
||||
.deleteVariantPrices(priceListId, variantId)
|
||||
.then(({ ids, object, deleted }) => {
|
||||
console.log(ids)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminDeletePriceListVariantPrices,
|
||||
} from "medusa-react"
|
||||
|
||||
const PriceList = () => {
|
||||
const deleteVariantPrices =
|
||||
useAdminDeletePriceListVariantPrices(
|
||||
priceListId,
|
||||
variantId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDeletePrices = () => {
|
||||
deleteVariantPrices.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```jsx
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/price-lists/${priceListId}/variants/${variantId}/prices`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ ids, object, deleted }) => {
|
||||
console.log(ids.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/price-lists/<PRICE_LIST_ID>/variants/<VARIANT_ID>/prices' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the IDs of the deleted prices.
|
||||
|
||||
---
|
||||
|
||||
## Delete Price List
|
||||
|
||||
You can delete a price list, and subsequently all prices defined in it, using the [Delete Price List](https://docs.medusajs.com/api/admin#price-lists_deletepricelistspricelist) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.admin.priceLists.delete(priceListId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeletePriceList } from "medusa-react"
|
||||
|
||||
const PriceList = () => {
|
||||
const deletePriceList = useAdminDeletePriceList(priceListId)
|
||||
// ...
|
||||
|
||||
const handleDeletePriceList = () => {
|
||||
deletePriceList.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PriceList
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URL>/admin/price-lists/${priceListId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.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/price-lists/<PRICE_LIST_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the ID of the deleted price list.
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
description: 'Learn how to override the price selection strategy. The price selection strategy is used to determine the best price based on a specific context.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override Price Selection Strategy
|
||||
|
||||
In this document, you’ll learn how to override Medusa’s price selection strategy to create a custom pricing strategy.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re interested in learning what a price selection strategy is and how it works, check out [this documentation](../price-selection-strategy.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## 1. Create Class
|
||||
|
||||
Create a TypeScript or JavaScript file in `src/strategies` of your Medusa backend project with a class that extends the `AbstractPriceSelectionStrategy` class:
|
||||
|
||||
```ts title=src/strategies/price.ts
|
||||
import {
|
||||
AbstractPriceSelectionStrategy,
|
||||
IPriceSelectionStrategy,
|
||||
PriceSelectionContext,
|
||||
PriceSelectionResult,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export default class MyStrategy extends
|
||||
AbstractPriceSelectionStrategy {
|
||||
withTransaction(
|
||||
manager: EntityManager
|
||||
): IPriceSelectionStrategy {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
return new MyStrategy()
|
||||
}
|
||||
|
||||
async calculateVariantPrice(
|
||||
variant_id: string,
|
||||
context: PriceSelectionContext
|
||||
): Promise<PriceSelectionResult> {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use services or repositories in the strategy by adding them to the constructor and updating the parameters passed to the `MyStrategy` constructor in `withTransaction`. For example:
|
||||
|
||||
```ts
|
||||
import {
|
||||
AbstractPriceSelectionStrategy,
|
||||
CustomerService,
|
||||
IPriceSelectionStrategy,
|
||||
PriceSelectionContext,
|
||||
PriceSelectionResult,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default class MyStrategy extends
|
||||
AbstractPriceSelectionStrategy {
|
||||
private customerService: CustomerService
|
||||
|
||||
constructor({
|
||||
customerService,
|
||||
}) {
|
||||
super()
|
||||
this.customerService = customerService
|
||||
}
|
||||
|
||||
withTransaction(
|
||||
manager: EntityManager
|
||||
): IPriceSelectionStrategy {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
return new MyStrategy({
|
||||
customerService: this.customerService,
|
||||
})
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Implement calculateVariantPrice
|
||||
|
||||
Implement the price selection strategy you want inside the `calculateVariantPrice` method.
|
||||
|
||||
This method accepts the variant ID as a first parameter and the [context](../price-selection-strategy.md#context-object) object as a second parameter.
|
||||
|
||||
This method must return an object having the following fields:
|
||||
|
||||
```ts noReport
|
||||
{
|
||||
originalPrice, // number | null
|
||||
calculatedPrice, // number | null
|
||||
prices // MoneyAmount[]
|
||||
}
|
||||
```
|
||||
|
||||
You can learn more about optional properties and the meaning behind every property [here](../price-selection-strategy.md#calculatevariantprice-method).
|
||||
|
||||
---
|
||||
|
||||
## 3. Run Build Command
|
||||
|
||||
In your terminal, run the build command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, try out your strategy using any of the [Products](https://docs.medusajs.com/api/store#products) or [Carts](https://docs.medusajs.com/api/store#carts) endpoints which include retrieving product variants and line items respectively. You should then see the prices in the response based on your implemented strategy.
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
description: "Price Lists are special prices applied to products based on a set of conditions. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Price Lists
|
||||
|
||||
Price Lists are special prices applied to products based on a set of conditions. This overview introduces the available features related to price lists.
|
||||
|
||||
:::note
|
||||
|
||||
Looking for Discounts? You can find the [Discounts guide here](../discounts/overview.mdx).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Price Lists Management
|
||||
|
||||
Admins can manage price lists to add special prices for products based on defined conditions, such as the group of the customer viewing the product. The price list can be used for a sale or to override prices of some products.
|
||||
|
||||
Developers can change the default logic behind how prices are selected to be shown to the customer.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/admin/manage-price-lists',
|
||||
label: 'Admin: Manage Price Lists',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage price lists using the Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/price-lists/manage',
|
||||
label: 'User Guide: Manage Price Lists',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage price lists using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/backend/override-price-selection-strategy',
|
||||
label: 'Backend: Override Price Selection',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to override the price selection strategy.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Import Prices
|
||||
|
||||
Admins can import prices into a price list from CSV files.
|
||||
|
||||
Developers can import the prices from CSV files using the Admin APIs. They can also customize the import strategy.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/price-lists/import',
|
||||
label: 'User Guide: Import Prices',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to import prices using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/admin/import-prices',
|
||||
label: 'Admin: Import Prices',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to import prices using the Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/batch-jobs/customize-import',
|
||||
label: 'Core: Customize Import Strategy',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to customize the prices import strategy.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how price list-related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists',
|
||||
label: 'Architecture: Price List',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Price List architecture.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/price-selection-strategy',
|
||||
label: 'Architecture: Price Selection Strategy',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about Price Selection Strategy.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Price List’s relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Product',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Price lists are used to override product’s prices.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/customers/overview',
|
||||
label: 'Customers',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Customer groups can be used as a price list’s conditions.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
description: 'Learn what price lists are and how they work in a Medusa backend. Price Lists can be used to override product prices based on different conditions.'
|
||||
---
|
||||
|
||||
# Price Lists
|
||||
|
||||
In this document, you’ll learn what price lists are and how they work.
|
||||
|
||||
## What are Price Lists
|
||||
|
||||
Price lists can be used to override products’ prices based on different conditions. Each price list you create can have its own options and conditions. A price list must override at least one product variant’s prices.
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
Price lists can be used for a variety of use cases including:
|
||||
|
||||
- Setting different prices for VIP customers.
|
||||
- Creating sales that run through a specific period of time.
|
||||
- Implement Buy X for Price Y sale model. In other words, buy X quantity of a product at the price of Y.
|
||||
|
||||
### Price List Entity Overview
|
||||
|
||||
A price list is stored in the database as a [PriceList](../../references/entities/classes/PriceList.md) entity. Some of its important attributes are:
|
||||
|
||||
- `type`: The price list's type. Can be either a `sale` or an `override`.
|
||||
- `status`: The status of the price list. Can be `active` or `draft`. If a price list is a `draft`, its prices won't be applied even if its conditions are met.
|
||||
- `starts_at`: The date to start applying the price list.
|
||||
- `ends_at`: The date to stop applying the price list.
|
||||
|
||||
---
|
||||
|
||||
## Price List Conditions
|
||||
|
||||
You can control when a price list is applied using a set of conditions. Only when these conditions are met can the price list be applied.
|
||||
|
||||
You can use any of the following conditions:
|
||||
|
||||
- **Start and/or expiry date**: You can set a start date, an end date, or both to define when the prices in the list should be applied.
|
||||
- **Customer group:** You can choose a customer group to apply the prices for. Customers that belong to this group will see the prices you set in the list, which customers that don’t belong to the group can’t see.
|
||||
- **Minimum and/or maximum quantity:** You can specify the minimum quantity, the maximum quantity, or both minimum and maximum quantities of a product variant required to be in the customer’s cart to apply a price in a price list.
|
||||
- **Region:** You can specify a specific region where a price in the price list is available in. Only customers accessing your store from that region can see these prices.
|
||||
- **Currency Code:** You can specify a currency code where a price in the price list is available in. Only customers accessing your store from regions that use this currency can see these prices.
|
||||
|
||||
---
|
||||
|
||||
## How are Price Lists Applied
|
||||
|
||||
When a product or a line item is retrieved or manipulated on the storefront, Medusa determines its price using a Price Selection Strategy. The price selection strategy determines the best price to apply in a given [context](./price-selection-strategy.md#context-object). Part of determining the price depends on the price list.
|
||||
|
||||
:::info
|
||||
|
||||
This section explains how the price selection strategy uses price lists when it determines the price of a product variant. If you want full details on how the price selection strategy works, check out this documentation instead.
|
||||
|
||||
:::
|
||||
|
||||
### Product Variants
|
||||
|
||||
When the strategy calculates the prices of a product variant, it retrieves both the original and the calculated prices of the variant.
|
||||
|
||||
The original price depends on the selected region or currency code in the current context, where the region has higher precedence.
|
||||
|
||||
The calculated price is the lowest price among all retrieved prices. Retrieved prices can include the original price and the price lists that can be applied. Prices are retrieved based on the [context](./price-selection-strategy.md#context-object).
|
||||
|
||||
In the [Get Product](https://docs.medusajs.com/api/store#products_getproductsproduct) and [List Product](https://docs.medusajs.com/api/store#products_getproducts) endpoints, you must pass either the `region_id` or `currency_code` to retrieve the correct prices, as they are part of the price selection strategy context.
|
||||
|
||||
Each variant in the response has the following properties:
|
||||
|
||||
- `original_price`: The price based on the region or currency selected. You can also retrieve the original price with tax from the `original_price_incl_tax` property.
|
||||
- `calculated_price`: The price retrieved from a price list, if there are any. If no price list matches the current context, `calculated_price` will have the same value as `original_price`. You can also retrieve the calculated price with tax from the `calculated_price_incl_tax` property.
|
||||
|
||||
If both the `region_id` and `currency_code` aren’t passed to the request, the values of these properties will be `null`.
|
||||
|
||||
### Line Items
|
||||
|
||||
The total of a line item depends on the price of a product variant. When the line item is created, the price of its underlying product variant is retrieved using the same process detailed in the previous section.
|
||||
|
||||
Then, the `unit_price` of the line item is set to the `calculated_price` property of the product variant.
|
||||
|
||||
When creating, retrieving, or updating the line item using the API endpoints, the line item’s totals are calculated. The following properties are returned as part of the endpoints’ responses:
|
||||
|
||||
- `unit_price`: The product variant’s calculated price.
|
||||
- `subtotal`: The `unit_price` multiplied by the quantity of the line item.
|
||||
|
||||
Since the line item belongs to a cart, there’s no need to pass the `region_id` or `currency_code` to the requests. The cart’s region and currency are used to determine the context of the price selection.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Price Selection Strategy Overview](./price-selection-strategy.md)
|
||||
- [Manage price lists using the admin APIs](./admin/manage-price-lists.mdx)
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
description: 'Learn what the price selection strategy is in the Medusa backend. The price selection strategy retrieves the best price for a product variant for a specific context.'
|
||||
---
|
||||
|
||||
# Price Selection Strategy
|
||||
|
||||
In this document, you’ll learn what a price selection strategy is.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re interested to learn how to override the price selection strategy, check out [this documentation](./backend/override-price-selection-strategy.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## What's a Price Selection Strategy
|
||||
|
||||
Medusa provides many features and different ways to control the price of a product variant. This includes price lists and their different conditions, products’ original prices, and taxes.
|
||||
|
||||
Medusa uses the `PriceSelectionStrategy` class to retrieve the best price for a product variant for a specific context. This strategy is used whenever products and line items are retrieved or manipulated on the storefront.
|
||||
|
||||
---
|
||||
|
||||
## PriceSelectionStrategy Overview
|
||||
|
||||
The `PriceSelectionStrategy` class extends the `AbstractPriceSelectionStrategy` class. Its main method is the `calculateVariantPrice`.
|
||||
|
||||
### calculateVariantPrice Method
|
||||
|
||||
Medusa uses this method to retrieve one or more product variants' prices. This method is used when retrieving product variants or their associated line items. It's also used when retrieving other entities that product variants and line items belong to, such as products and carts respectively.
|
||||
|
||||
This method accepts two parameters:
|
||||
|
||||
1. The first parameter is an array of objects, each object having the following properties:
|
||||
1. `variantId`: a string indicating the ID of the variant to calculate the price for.
|
||||
2. `quantity`: an optional number indicating the quantity of the variant.
|
||||
2. A [context](#context-object) object.
|
||||
|
||||
The method retrieves all the available prices of the variant based on the conditions in the context object.
|
||||
|
||||
It returns an object with the following properties:
|
||||
|
||||
1. `originalPrice`: The original price of the variant which depends on the selected region or currency code in the context object. If both region ID and currency code are available in the context object, the region has higher precedence.
|
||||
2. `originalPriceIncludesTax`: A boolean value indicating whether the original price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
3. `calculatedPrice`: The lowest price among the prices of the product variant retrieved using the context object.
|
||||
4. `calculatedPriceIncludesTax`: A boolean value indicating whether the calculated price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
5. `calculatedPriceType`: Either `default` if the `calculatedPrice` is the original price, or the type of the price list applied.
|
||||
6. `prices`: an array of all the prices of the variant retrieved using the context object. It can include its original price and its price lists if there are any.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about price lists and how they’re used in [this documentation](./price-lists.md).
|
||||
|
||||
:::
|
||||
|
||||
### Context Object
|
||||
|
||||
The context that is passed to the `calculateVariantPrice` method is an object that has the following optional properties:
|
||||
|
||||
- `cart_id`: A string indicating the ID of the customer’s cart. This is used when the prices are being retrieved for the variant of a line item, as it is used to determine the current region and currency code of the context.
|
||||
- `customer_id`: A string indicating the ID of the customer. This is used to filter out price lists for a customer group that this customer doesn’t belong to.
|
||||
- `quantity`: A number indicating the quantity of the item in the cart. This is used to filter out price lists that have `min_quantity` or `max_quantity` conditions set.
|
||||
- `region_id`: A string indicating the ID of the region the customer is using.
|
||||
- `currency_code`: A string indicating the currency code the customer is using.
|
||||
- `include_discount_prices`: A boolean value indicating whether price list prices should be retrieved or not.
|
||||
- `tax_rates`: An array of objects indicating the tax rates to be applied. This is only used for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
- `ignore_cache`: a boolean value indicating whether to calculate the prices even if the value of an earlier price calculation is available in the cache.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Override the Price Selection Strategy](./backend/override-price-selection-strategy.md)
|
||||
@@ -0,0 +1,411 @@
|
||||
---
|
||||
description: 'Learn how to import products into Medusa using the Admin REST APIs. The steps include uploading a CSV file, creating a batch job for the import, and confirming the batch job.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Bulk Import Products
|
||||
|
||||
In this document, you’ll learn how to use the Admin APIs to bulk import products into a Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Using Medusa’s [Batch Job Admin APIs](https://docs.medusajs.com/api/admin#batch-jobs_getbatchjobs), you can import products into your Medusa backend. This will either add new products or update existing products.
|
||||
|
||||
---
|
||||
|
||||
## 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. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### File Service Plugin
|
||||
|
||||
Part of the process of importing products is uploading a CSV file. This requires a file service plugin to be installed on your backend. If you don’t have any installed, you can install one of the following options:
|
||||
|
||||
- [MinIO](../../../plugins/file-service/minio.md) (At least version `1.1.0`)
|
||||
- [Spaces](../../../plugins/file-service/spaces.md)
|
||||
|
||||
:::note
|
||||
|
||||
The local file service can't be used for product import.
|
||||
|
||||
:::
|
||||
|
||||
### CSV File
|
||||
|
||||
You must have a CSV file that you will use to import products into your Medusa backend. You can check [this CSV example file](https://medusa-doc-files.s3.amazonaws.com/product-import-sales-channels.csv) to see which format is required for your import.
|
||||
|
||||
### 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## 1. Upload CSV File
|
||||
|
||||
The first step is to upload the CSV file that you want to import products.
|
||||
|
||||
You can do that by sending the following request to the [Upload Files](https://docs.medusajs.com/api/admin#uploads_postuploads) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.uploads.create(file) // file is an instance of File
|
||||
.then(({ uploads }) => {
|
||||
const key = uploads[0].key
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUploadFile } from "medusa-react"
|
||||
|
||||
const ImportProducts = () => {
|
||||
const uploadFile = useAdminUploadFile()
|
||||
// ...
|
||||
|
||||
const handleFileUpload = (file: File) => {
|
||||
uploadFile.mutate(file)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
const formData = new FormData()
|
||||
formData.append("files", file) // file is an instance of File
|
||||
|
||||
fetch(`<BACKEND_URL>/admin/uploads`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ uploads }) => {
|
||||
const key = uploads[0].key
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/uploads' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-F 'files=@"<FILE_PATH_1>"'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of uploads. Each item in the array is an object that includes the properties `url` and `key`. You’ll need the `key` to import the products next.
|
||||
|
||||
Where `<FILE_PATH_1>` is a full path to the file. For example, `/Users/medusa/product-import-sales-channels.csv`
|
||||
|
||||
---
|
||||
|
||||
## 2. Create a Batch Job for Product Import
|
||||
|
||||
To start a new product import, you must create a batch job.
|
||||
|
||||
You can do that by sending the following request to the [Create a Batch Job](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.create({
|
||||
type: "product-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
},
|
||||
dry_run: true,
|
||||
})
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateBatchJob } from "medusa-react"
|
||||
|
||||
const ImportProducts = () => {
|
||||
const createBatchJob = useAdminCreateBatchJob()
|
||||
// ...
|
||||
|
||||
const handleCreateBatchJob = () => {
|
||||
createBatchJob.mutate({
|
||||
type: "product-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
},
|
||||
dry_run: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "product-import",
|
||||
context: {
|
||||
fileKey: key, // obtained from previous step
|
||||
},
|
||||
dry_run: true,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"type": "product-import",
|
||||
"context": {
|
||||
"fileKey": "<KEY>"
|
||||
},
|
||||
"dry_run": true
|
||||
}'
|
||||
# <KEY> is the key you obtained from the previous step
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In the body of the request, you must pass the following parameters:
|
||||
|
||||
- `type`: Batch jobs can be of different types. For product imports, the type should always be `product-import`.
|
||||
- `context`: An object that must contain the `fileKey` property. The value of this property is the key received when you uploaded the CSV file.
|
||||
- `dry_run`: This is optional to include. If not set or if its value is `false`, the product import will start right after you send this request. Settings its value to `true` allows you to retrieve afterward a brief of the number of products that will be added or updated, or the number of rejected products.
|
||||
|
||||
This request returns the batch job with its details such as the `status` or `id`.
|
||||
|
||||
:::note
|
||||
|
||||
If you don’t set `dry_run` or you set it to `false`, you don’t need to follow the rest of these steps.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Retrieve Batch Job
|
||||
|
||||
After creating the batch job, it will be pre-processed. At this point, the CSV file will be validated, and the number of products to add and update, or that are rejected are counted.
|
||||
|
||||
You can retrieve all the details of the batch job, including its status and the brief statistics related to the products by sending the following request:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.retrieve(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminBatchJob } from "medusa-react"
|
||||
|
||||
const ImportProducts = () => {
|
||||
const { batch_job, isLoading } = useAdminBatchJob(batchJobId)
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* ... */}
|
||||
{isLoading && <span>Loading</span>}
|
||||
{batch_job && (
|
||||
<span>
|
||||
Status: {batch_job.status}.
|
||||
Number of Products: {batch_job.result.count}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the batch job’s ID as a parameter, which can be retrieved from the previous request. It returns the batch job in the response.
|
||||
|
||||
If the batch job has been pre-processed, the status of the batch job will be `pre_processed` and the `result` property will contain details about the import.
|
||||
|
||||
Here’s an example of the `result` property:
|
||||
|
||||
```json noReport
|
||||
"result": {
|
||||
"count": 5, // Total number of products to be created or updated
|
||||
"stat_descriptors": [ //details about the products to be created/updated
|
||||
{
|
||||
"key": "product-import-count",
|
||||
"name": "Products/variants to import",
|
||||
"message": "There will be 2 products created..."
|
||||
}
|
||||
],
|
||||
"advancement_count": 0 //number of products processed so far.
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Confirm Batch Job
|
||||
|
||||
A batch job can be confirmed only once the batch job has the status `pre_processed`. Once you confirm a batch job, the product import will start which will create or update products on your backend.
|
||||
|
||||
To confirm a batch job send the following request:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.confirm(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminConfirmBatchJob } from "medusa-react"
|
||||
|
||||
const ImportProducts = () => {
|
||||
const confirmBatchJob = useAdminConfirmBatchJob(batchJobId)
|
||||
// ...
|
||||
|
||||
const handleConfirmJob = () => {
|
||||
confirmBatchJob.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ImportProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}/confirm`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>/confirm' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the batch job as a path parameter and returns the updated batch job. The returned batch job should have the status `confirmed`, which indicates that the batch job will now start processing.
|
||||
|
||||
### Checking the Status After Confirmation
|
||||
|
||||
After confirming the batch job, you can check the status while it is processing at any given point by [retrieving the batch job](#optional-retrieve-batch-job). Based on the status, you can infer the progress of the batch job:
|
||||
|
||||
- If the status is `processing`, it means that the import is currently in progress. You can also check `result.advancement_count` to find out how many products have been created or updated so far.
|
||||
- If the status is `failed`, it means an error has occurred during the import. You can check the error in `result.errors`.
|
||||
- If the status is `completed`, it means the import has finished successfully.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Batch Jobs Overview](../../../development/batch-jobs/index.mdx)
|
||||
@@ -0,0 +1,684 @@
|
||||
---
|
||||
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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Categories
|
||||
|
||||
You can retrieve available categories by sending a request to the [List Categories](https://docs.medusajs.com/api/admin#product-categories_getproductcategories) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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 Categories</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](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
You can also pass filters and other selection query parameters to the request. Check out the [API reference](https://docs.medusajs.com/api/admin#product-categories_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](https://docs.medusajs.com/api/admin#product-categories_postproductcategories) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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](https://docs.medusajs.com/api/admin#product-categories_getproductcategoriescategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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](https://docs.medusajs.com/api/admin#product-categories_postproductcategoriescategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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](https://docs.medusajs.com/api/admin#product-categories_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](https://docs.medusajs.com/api/admin#products). 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](https://docs.medusajs.com/api/admin#product-categories_postproductcategoriescategoryproductsbatch) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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](https://docs.medusajs.com/api/admin#product-categories_deleteproductcategoriescategoryproductsbatch) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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](https://docs.medusajs.com/api/admin#product-categories_deleteproductcategoriescategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<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.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to use product categories in a storefront](../storefront/use-categories.mdx)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,96 @@
|
||||
---
|
||||
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.
|
||||
- `description`: An optional string used to add a description for 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 hierarchy 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)
|
||||
- [How to use product categories in a storefront](./storefront/use-categories.mdx)
|
||||
@@ -0,0 +1,215 @@
|
||||
---
|
||||
description: "Products are items or services that businesses sell to their customers. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Products
|
||||
|
||||
Products are items or services that businesses sell to their customers. This overview introduces the available features related to products.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Products user guide](../../user-guide/products/manage.mdx).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Products Management
|
||||
|
||||
Admins can manage unlimited amount of products and their variants. They can manage the product’s details including their pricing, inventory, and more.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/admin/manage-products',
|
||||
label: 'Admin: Manage Products',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage products using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/products/manage',
|
||||
label: 'User Guide: Products',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Manage products in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/storefront/show-products',
|
||||
label: 'Storefront: Show Products',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to show products in a storefront.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Product Organization
|
||||
|
||||
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: '/modules/products/admin/manage-categories',
|
||||
label: 'Admin: Manage Categories',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage categories using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/products/categories',
|
||||
label: 'User Guide: Categories',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage categories in the Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/storefront/use-categories',
|
||||
label: 'Storefront: Use Categories',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Learn how to use categories in a storefront.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Product Import and Export
|
||||
|
||||
Admins can bulk import and export products using CSV files. This facilitates moving data from other ecommerce platforms to Medusa.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/admin/import-products',
|
||||
label: 'Admin: Import Products',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to import products into Medusa using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/products/import',
|
||||
label: 'User Guide: Import',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to import products into Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/products/export',
|
||||
label: 'User Guide: Export',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to export products from Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how product-related entities are build, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products',
|
||||
label: 'Architecture: Product',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Product Architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/categories',
|
||||
label: 'Architecture: Category',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Product Category Architecture.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Products’ relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/overview',
|
||||
label: 'Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['channels-solid'],
|
||||
description: 'Products availability can be specific to sales channels.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/overview',
|
||||
label: 'Price Lists',
|
||||
customProps: {
|
||||
icon: Icons['currency-dollar'],
|
||||
description: 'Override prices of products based on specific conditions.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/overview',
|
||||
label: 'Discounts',
|
||||
customProps: {
|
||||
icon: Icons['receipt-percent'],
|
||||
description: 'Discounts can be created for specific products or collections.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/overview',
|
||||
label: 'Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['gift-solid'],
|
||||
description: 'Gift cards can be sold as Products.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Carts and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Allow customers to purchase your products.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/overview',
|
||||
label: 'Orders',
|
||||
customProps: {
|
||||
icon: Icons['check-circle-solid'],
|
||||
description: 'Product purchases can be managed as orders.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
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.'
|
||||
---
|
||||
|
||||
# Products Architecture Overview
|
||||
|
||||
In this document, you’ll learn about Products in Medusa and their relation to other entities.
|
||||
|
||||
## Overview
|
||||
|
||||
Products are items that a business sells to customers. Each product can have options and variants. Options are the different available attributes of a product, and variants are the salable combinations of these options.
|
||||
|
||||
For example, a product can have a “Color” option with values blue and green. You can then create two product variants from these options: one using the option value blue, and the other using the value green. This is just a simple example, as you can have multiple options and have variants combine values from each of these options.
|
||||
|
||||
Products can be associated with categories, collections, types, and more. This allows merchants to better organize products either internally or for their customers.
|
||||
|
||||
---
|
||||
|
||||
## Product Entity Overview
|
||||
|
||||
The `Product` entity has many useful attributes, including:
|
||||
|
||||
- `title`: The name of the product.
|
||||
- `handle`: A slug representation of the product’s title. This can be used in the storefront to generate human-readable URLs for products, which can be beneficial for Search Engine Optimization (SEO) purposes.
|
||||
- `status`: A string indicating the status of the product. Its values can be `draft`, `proposed`, `published`, and `rejected`.
|
||||
- `external_id`: A string that can be used to store an ID of the product on an external system. This is useful if you’re migrating the product from an ecommerce platform to Medusa, or if you’re connecting Medusa’s products to an external service.
|
||||
|
||||
Other attributes of the `Product` entity are explored in the next sections.
|
||||
|
||||
### Customizing the Product Entity
|
||||
|
||||
It’s common for developers to customize the `Product` entity, as there can be different use cases for each type of businesses.
|
||||
|
||||
In some simple cases, you might just need to store additional values within the entity without fully customizing it. For those cases, you can use the `metadata` attribute. This is an object stored in the database as a JSONB type in the database. You can store inside it key-value data that is useful for your case:
|
||||
|
||||
```tsx
|
||||
metadata = {
|
||||
"someKey": "someValue",
|
||||
}
|
||||
```
|
||||
|
||||
In more complex scenarios, you may need to add columns or relations to your `Product` entity. You can then [extend the entity](../../development/entities/extend-entity.md) to make your customizations.
|
||||
|
||||
---
|
||||
|
||||
## Product Option Overview
|
||||
|
||||
Each product is expected to have at least one option. Options are used to specify the different available properties of that product. Some examples of options are colors, sizes, or material.
|
||||
|
||||
Product Options are represented by the `ProductOption` entity. The entity has the attribute `title` indicating the name of the option (for example, Color). The entity is associated with the `Product` entity through the `product_id` attribute and the `product` relation. You can also access the options of a product from the `Product` entity using the `options` relation.
|
||||
|
||||
The available values of each option are represented by the `ProductOptionValue` entity. The entity has the attribute `value` which is a string holding the value of the option (for example, Blue).
|
||||
|
||||
The `ProductOptionValue` entity is associated with the `ProductOption` through the `option_id` attribute and the `option` relation. You can also access the values of an option from the `ProductOption` entity using the `values` relation.
|
||||
|
||||
The `ProductOptionValue` entity is also associated with a product variant using the `variant_id` attribute and the `variant` relation.
|
||||
|
||||
---
|
||||
|
||||
## Product Variant Overview
|
||||
|
||||
Product variants are the actual salable item in your store. Each variant is a combination of the different option values available on the product. For example, if you have a color option and a size option, here are some variants that you might have:
|
||||
|
||||
- Variant A: Color blue and size large
|
||||
- Variant B: Color green and size large
|
||||
- Variant C: Color blue and size small
|
||||
- Variant D: Color green and size small
|
||||
|
||||
And the list can go on.
|
||||
|
||||
:::note
|
||||
|
||||
You can’t have two product variants with the same values. For example, you can’t have another Variant E having the same option values as Variant B.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
Product variants are represented by the `ProductVariant` entity. The entity’s attributes include:
|
||||
|
||||
- `title`: A string title of the product variant. This is different from the title in the `Product` entity.
|
||||
- `product_id`: A string indicating which product this variant belongs to. You can also access the product through the `product` relation if it’s expanded.
|
||||
- `sku`: A string indicating the Stock Keeping Unit (SKU) of the variant. This, along with other attributes like `barcode` or `upc` are useful to store inventory-related information.
|
||||
- `inventory_quantity`: A number indicating the available inventory of the variant. This is only useful if you have `manage_inventory` disabled.
|
||||
- `manage_inventory`: A boolean value indicating whether Medusa should handle the management of the product variant’s inventory. This would allow you to handle your inventory data more accurately across different locations. You can learn more in [the Inventory Module documentation](../multiwarehouse/inventory-module.md).
|
||||
- `inventory_items`: A relation that gives you access to the inventory items of a variant when `manage_inventory` is enabled. You can learn more in [the Inventory Module documentation](../multiwarehouse/inventory-module.md).
|
||||
- `variant_rank`: a number that can be used to decide the sort order of variants in a storefront.
|
||||
|
||||
You can also access the option values of a product variant through the `options` relation, which is an array of `ProductOptionValue` items.
|
||||
|
||||
### Product Variant Pricing
|
||||
|
||||
As the product variant is the salable item in your store, the price of a product is specific to each product variant.
|
||||
|
||||
A product variant can have more than one price for different context. For example, a product variant can have a different price for each currency or region.
|
||||
|
||||
The prices of a product variant are available on the `prices` relation, which is an array of `MoneyAmount` items. `MoneyAmount` is an entity used throughout Medusa to store prices in general.
|
||||
|
||||
The `MoneyAmount` entity has the following attributes that are useful for a product variant:
|
||||
|
||||
- `currency_code`: A string indicating the currency code this price is for. The currency can also be accessed through the `currency` relation.
|
||||
- `amount`: A number indicating the price.
|
||||
- `region_id`: An optional string indicating the ID of the region this price is for. The region can also be accessed through the `region` relation.
|
||||
- `price_list_id`: An optional string indicating the ID of the price list this price is for. The price list can also be accessed through the `price_list` relation.
|
||||
|
||||
### Storing the Product Variant’s Price
|
||||
|
||||
You must store the `amount` in a currency's smallest unit as an integer.
|
||||
|
||||
For example, if a variant's price is 10 USD, it's stored is `1000`, as in 1,000 cents.
|
||||
|
||||
For zero-decimal currencies, the amount is still stored as an integer without multiplying by a 100. For example, if a variant's price is ¥500, it's stored as `500`.
|
||||
|
||||
<details>
|
||||
<summary>Zero-decimal Currencies</summary>
|
||||
|
||||
- BIF
|
||||
- CLP
|
||||
- DJF
|
||||
- GNF
|
||||
- JPY
|
||||
- KMF
|
||||
- KRW
|
||||
- MGA
|
||||
- PYG
|
||||
- RWF
|
||||
- UGX
|
||||
- VND
|
||||
- VUV
|
||||
- XAF
|
||||
- XOF
|
||||
- XPF
|
||||
|
||||
</details>
|
||||
|
||||
This logic of formatting the price is not handled in the backend. So, when you add a product variant using the Admin APIs, you must format the price as explained earlier. The backend stores the price as received from API requests.
|
||||
|
||||
In addition, the Medusa admin dashboard and the Next.js Starter Template expect the price to be of that format, so when prices are displayed for currencies that are stored multiplied by a `100`, such as USD, they’re divided by a hundred. Also, when you add or update a product variant, its price is sent to the Medusa backend as the price multiplied by a hundred.
|
||||
|
||||
### Displaying the Product Variant’s Price
|
||||
|
||||
When showing a product’s details to the customer in the storefront, you can pass in your requests query parameters that specify the customer’s context. For example, you can specify the customer’s region. This would retrieve the correct pricing of the product for each customer. You can learn more in [this documentation](./storefront/show-products.mdx#product-pricing-parameters).
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
This section explores the relations between the `Product` entity and other entities in your Medusa backend.
|
||||
|
||||
### SalesChannel
|
||||
|
||||
[Sales channels](../sales-channels/sales-channels.md) define the different channels a business is selling its products in. A product can be available in some or all sales channels.
|
||||
|
||||
You can access a product’s sales channels by expanding the `sales_channels` relation and accessing `product.sales_channels`. It’s an array of sales channels the product is available in.
|
||||
|
||||
### ProductType
|
||||
|
||||
A product can belong to a type, which is represented by the `ProductType` entity.
|
||||
|
||||
The ID of the product’s type is stored under the attribute `type_id`. You can access the product’s type by expanding the `type` relation and accessing `product.type`.
|
||||
|
||||
### ProductTag
|
||||
|
||||
A product can be associated with one or more tags. A tag is represented by the `ProductTag` entity.
|
||||
|
||||
You can access the product’s tags by expanding the `tags` relation and accessing `product.tags`, which is an array of product tags.
|
||||
|
||||
### ProductCollection
|
||||
|
||||
A product can be associated with a collection. A collection is represented by the `ProductCollection` entity.
|
||||
|
||||
The ID You can access the product’s collection by expanding the `collection` relation and accessing `product.collection`.
|
||||
|
||||
### ProductCategory
|
||||
|
||||
A product can be associated with a category. A [category](./categories.md) is represented by the `ProductCategory` entity.
|
||||
|
||||
You can access the product’s categories by expanding the `categories` relation and accessing `product.categories`, which is an array of product categories.
|
||||
|
||||
### DiscountConditionProduct
|
||||
|
||||
A [discount](../discounts/discounts.md) can be specified for one or more products through discount conditions. The relation between a discount condition and a product is established through the `DiscountConditionProduct` entity which holds the IDs of the product and the discount condition.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to show products in a storefront](./storefront/show-products.mdx)
|
||||
@@ -0,0 +1,386 @@
|
||||
---
|
||||
title: 'How to Use Serverless Product Module'
|
||||
description: 'Learn how to use the product module in a serverless setup by installing it in a Next.js application.'
|
||||
addHowToData: true
|
||||
badge:
|
||||
variant: orange
|
||||
text: beta
|
||||
---
|
||||
|
||||
In this document, you’ll learn how to use the Product module in a serverless setup.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s modules increase extensibility and customization capabilities. Instead of having to manage a fully-fledged ecommerce system, you can use these modules in serverless applications, such as a Next.js project.
|
||||
|
||||
The module only needs to connect to a PostgreSQL database. You can also connect it to an existing Medusa database. Then, you can use the Product module to connect directly to the PostgreSQL database and retrieve or manipulate data.
|
||||
|
||||
This guide explains how to use the Product Module in a Next.js application as an example. You can use it with other frameworks, such as Remix, as well.
|
||||
|
||||
### Benefits of Serverless Modules
|
||||
|
||||
- Keep packages small enough to be deployed to serverless Infrastructure easily
|
||||
- Bring Medusa closer to Edge runtime compatibility.
|
||||
- Make it easier to integrate modules into an ecosystem of existing commerce services.
|
||||
- Make it easier to customize or extend core logic in Medusa.
|
||||
|
||||
:::info
|
||||
|
||||
The product module is currently in beta, and it provides only functionalities related to browsing products. In later versions, the product module would include more powerful ecommerce features.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The Product Module requires a project using Node v16+.
|
||||
- The Product Module must connect to a PostgreSQL database. You can refer to [this guide](../../development/backend/prepare-environment.mdx#postgresql) to learn how to install PostgreSQL locally. Alternatively, you can use free PostgreSQL database hosting, such as [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres). If you have an existing Medusa database, you can use it as well.
|
||||
|
||||
---
|
||||
|
||||
## Installation in Next.js Project
|
||||
|
||||
This section explains how to install the Product module in a Next.js project.
|
||||
|
||||
If you don’t have a Next.js project, you can create one with the following command:
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest
|
||||
```
|
||||
|
||||
Refer to the [Next.js documentation](https://nextjs.org/docs/getting-started/installation) for other available installation options.
|
||||
|
||||
### Step 1: Install Product Module
|
||||
|
||||
In the root directory of your Next.js project, run the following command to install the product module:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/product
|
||||
```
|
||||
|
||||
### Step 2: Add Database Configurations
|
||||
|
||||
Create a `.env` file and add the following environment variable:
|
||||
|
||||
```bash
|
||||
POSTGRES_URL=<DATABASE_URL>
|
||||
```
|
||||
|
||||
Where `<DATABASE_URL>` is your database connection URL of the format `postgres://[user][:password]@[host][:port]/[dbname]`. You can learn more about the connection URL format in [this guide](../../development/backend/configurations.md#postgresql-configurations).
|
||||
|
||||
You can also set the following optional environment variables:
|
||||
|
||||
- `POSTGRES_SCHEMA`: a string indicating the PostgreSQL schema to use. By default, it's `public`.
|
||||
- `POSTGRES_DRIVER_OPTIONS`: a stringified JSON object indicating the PostgreSQL options to use. The JSON object is then parsed to be used as a JavaScript object. By default, it's `{"connection":{"ssl":false}}` for local PostgreSQL databases, and `{"connection":{"ssl":{"rejectUnauthorized":false}}}` for remote databases.
|
||||
|
||||
:::note
|
||||
|
||||
If `POSTGRES_DRIVER_OPTIONS` is not specified, the PostgreSQL database is considered local if `POSTGRES_URL` includes `localhost`. Otherwise, it's considered remote.
|
||||
|
||||
:::
|
||||
|
||||
### Step 3: Run Database Migrations
|
||||
|
||||
:::note
|
||||
|
||||
If you are using an existing Medusa database, you can skip this step.
|
||||
|
||||
:::
|
||||
|
||||
Migrations are used to create your database schema. Before you can run migrations, add in your `package.json` the following scripts:
|
||||
|
||||
```json title=package.json
|
||||
"scripts": {
|
||||
//...other scripts
|
||||
"product:migrations:run": "medusa-product-migrations-up",
|
||||
"product:seed": "medusa-product-seed ./seed-data.js"
|
||||
},
|
||||
```
|
||||
|
||||
The first command runs the migrations, and the second command allows you to optionally seed your database with demo products. However, you’d need the following seed file added in the root of your Next.js directory:
|
||||
|
||||
<details>
|
||||
<summary>Seed file</summary>
|
||||
|
||||
```js title=seed-data.js
|
||||
const productCategoriesData = [
|
||||
{
|
||||
id: "category-0",
|
||||
name: "category 0",
|
||||
parent_category_id: null,
|
||||
},
|
||||
{
|
||||
id: "category-1",
|
||||
name: "category 1",
|
||||
parent_category_id: "category-0",
|
||||
},
|
||||
{
|
||||
id: "category-1-a",
|
||||
name: "category 1 a",
|
||||
parent_category_id: "category-1",
|
||||
},
|
||||
{
|
||||
id: "category-1-b",
|
||||
name: "category 1 b",
|
||||
parent_category_id: "category-1",
|
||||
is_internal: true,
|
||||
},
|
||||
{
|
||||
id: "category-1-b-1",
|
||||
name: "category 1 b 1",
|
||||
parent_category_id: "category-1-b",
|
||||
},
|
||||
]
|
||||
|
||||
const productsData = [
|
||||
{
|
||||
id: "test-1",
|
||||
title: "product 1",
|
||||
status: "published",
|
||||
descriptions: "Lorem ipsum dolor sit amet, consectetur.",
|
||||
tags: [
|
||||
{
|
||||
id: "tag-1",
|
||||
value: "France",
|
||||
},
|
||||
],
|
||||
categories: [
|
||||
{
|
||||
id: "category-0",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "test-2",
|
||||
title: "product",
|
||||
status: "published",
|
||||
descriptions: "Lorem ipsum dolor sit amet, consectetur.",
|
||||
tags: [
|
||||
{
|
||||
id: "tag-2",
|
||||
value: "Germany",
|
||||
},
|
||||
],
|
||||
categories: [
|
||||
{
|
||||
id: "category-1",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const variantsData = [
|
||||
{
|
||||
id: "test-1",
|
||||
title: "variant title",
|
||||
sku: "sku 1",
|
||||
product: { id: productsData[0].id },
|
||||
inventory_quantity: 10,
|
||||
},
|
||||
{
|
||||
id: "test-2",
|
||||
title: "variant title",
|
||||
sku: "sku 2",
|
||||
product: { id: productsData[1].id },
|
||||
inventory_quantity: 10,
|
||||
},
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
productCategoriesData,
|
||||
productsData,
|
||||
variantsData,
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Then run the first and optionally second commands to migrate the database schema:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run product:migrations:run
|
||||
# optionally
|
||||
npm run product:seed
|
||||
```
|
||||
|
||||
### Step 4: Adjust Next.js Configurations
|
||||
|
||||
Next.js uses Webpack for compilation. Since quite a few of the dependencies used by the product module are not Webpack optimized, you have to add the product module as an external dependency.
|
||||
|
||||
To do that, add the `serverComponentsExternalPackages` option in `next.config.js`:
|
||||
|
||||
```js title=next.config.js
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: [
|
||||
"@medusajs/product",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
### Step 5: Create API Route
|
||||
|
||||
The product module is ready for use now! You can now use it to create API endpoints within your Next.js application.
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses Next.js's App Router.
|
||||
|
||||
:::
|
||||
|
||||
For example, create the file `app/api/products/route.ts` with the following content:
|
||||
|
||||
```ts title=app/api/products/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.list()
|
||||
|
||||
return NextResponse.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
This creates a `GET` endpoint at the route `/api/products`. You import the `initialize` function, aliased as `initializeProductModule`, from `@medusajs/product`. Then, in the endpoint, you invoke the `initializeProductModule` function, which returns an instance of the `ProductModuleService`. Using the product module service’s `list` method, you retrieve all available products and return them in the response of the endpoint.
|
||||
|
||||
### Step 6: Test Next.js Application
|
||||
|
||||
To test the endpoint you added, start your Next.js application with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, open in your browser the URL `http://localhost:3000/api/products`. If you seeded your database with demo products, or you’re using a Medusa database schema, you’ll receive the products in your database. Otherwise, the request will return an empty array.
|
||||
|
||||
---
|
||||
|
||||
## Example Usages
|
||||
|
||||
This section includes some examples of the different functionalities or ways you can use the product module. The code snippets are shown in the context of endpoints.
|
||||
|
||||
### List Products
|
||||
|
||||
```ts title=app/api/products/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.list()
|
||||
|
||||
return NextResponse.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve Product by Id
|
||||
|
||||
```ts title=app/api/product/[id]/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { id } = params
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.list({
|
||||
id,
|
||||
})
|
||||
|
||||
return NextResponse.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve Product by Handle
|
||||
|
||||
```ts title=app/api/product/[handle]/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { handle } = params
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.list({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve Categories
|
||||
|
||||
```ts title=app/api/categories/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.listCategories()
|
||||
|
||||
return NextResponse.json({ categories: data })
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve Category by Handle
|
||||
|
||||
```ts title=app/api/category/[handle]/route.ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { handle } = params
|
||||
const productService = await initializeProductModule()
|
||||
|
||||
const data = await productService.listCategories({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ category: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Products Commerce Module Overview](./overview.mdx)
|
||||
- [How to show products in a storefront](./storefront/show-products.mdx)
|
||||
- [How to show categories in a storefront](./storefront/use-categories.mdx)
|
||||
@@ -0,0 +1,840 @@
|
||||
---
|
||||
description: 'Learn how to show products in your storefront using the Store REST APIs. This includes listing products, showing the price of a product, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Badge from '@site/src/components/Badge';
|
||||
|
||||
# How to Show Products on the Storefront
|
||||
|
||||
In this document, you’ll learn how to show products in your storefront using the Store REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the products store REST APIs, you can display products on your storefront along with their different details.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following storefront functionalities:
|
||||
|
||||
- List products with filters.
|
||||
- Display product prices.
|
||||
- Search products.
|
||||
- Retrieve details of a single product by ID or by handle.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### @medusajs/product Module
|
||||
|
||||
This guide also includes code snippets to utilize the `@medusajs/product` module in your storefront, among other methods.
|
||||
|
||||
If you follow the `@medusajs/product` code blocks, it's assumed you already have the [@medusajs/product](../serverless-module.md) installed.
|
||||
|
||||
---
|
||||
|
||||
## List Products
|
||||
|
||||
You can list available products using the [List Products endpoint](https://docs.medusajs.com/api/store#products_getproducts):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list()
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)} attributes={{
|
||||
badge: true
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const products = await productService.list()
|
||||
|
||||
console.log(products.length)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any parameters. You can pass it parameters related to pagination, filtering, and more as explained in the [API reference](https://docs.medusajs.com/api/store#tag/Products/operation/GetProducts).
|
||||
|
||||
The request returns an array of product objects along with [pagination parameters](https://docs.medusajs.com/api/store#section/Pagination).
|
||||
|
||||
### Filtering Retrieved Products
|
||||
|
||||
The List Products endpoint accepts different query parameters that allow you to filter through retrieved results.
|
||||
|
||||
For example, you can filter products by a category ID:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
category_id: ["cat_123"],
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
category_id: ["cat_123"],
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)} attributes={{
|
||||
badge: true
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const products = await productService.list({
|
||||
category_ids: ["cat_123"],
|
||||
})
|
||||
|
||||
console.log(products)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?category_id[]=cat_123`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?category_id[]=cat_123'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will retrieve only products that belong to that category.
|
||||
|
||||
### Expand Categories
|
||||
|
||||
To expand the categories of each product, you can pass `categories` to the `expand` query parameter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
expand: "categories",
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
expand: "categories",
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)} attributes={{
|
||||
badge: true
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const products = await productService.list({}, {
|
||||
relations: ["categories"],
|
||||
})
|
||||
|
||||
console.log(products)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?expand=categories`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?expand=categories'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can learn more about the [expand parameter in the API reference](https://docs.medusajs.com/api/store#section/Expanding-Fields)
|
||||
|
||||
### Product Pricing Parameters
|
||||
|
||||
By default, the prices are retrieved based on the default currency associated with a store. You can use the following query parameters to ensure you are retrieving correct pricing based on the customer’s context:
|
||||
|
||||
- `region_id`: The ID of the customer’s region.
|
||||
- `cart_id`: The ID of the customer’s cart.
|
||||
- `currency_code`: The code of the currency to retrieve prices for.
|
||||
|
||||
It’s recommended to always include the cart and region’s IDs when you’re listing or retrieving a single product’s details, as it’ll show you the correct pricing fields as explained in the next section.
|
||||
|
||||
For example:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
cart_id,
|
||||
region_id,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
cart_id,
|
||||
region_id,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?cart_id=${cartId}®ion_id=${regionId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?cart_id=<CART_ID>®ion_id=<REGION_ID>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Display Product Price
|
||||
|
||||
Each product object in the retrieved array has a `variants` array. Each item in the `variants` array is a product variant object.
|
||||
|
||||
Product prices are available for each variant in the product. Each variant has a `prices` array with all the available prices in the context. However, when displaying the variant’s price, you’ll use the following properties inside a variant object:
|
||||
|
||||
- `original_price`: The original price of the product variant.
|
||||
- `calculated_price`: The calculated price, which can be based on prices defined in a price list.
|
||||
- `original_tax`: The tax amount applied to the original price, if any.
|
||||
- `calculated_tax`: The tax amount applied to the calculated price, if any.
|
||||
- `original_price_incl_tax`: The price after applying the tax amount on the original price.
|
||||
- `calculated_price_incl_tax`: The price after applying the tax amount on the calculated price
|
||||
|
||||
Typically, you would display the `calculated_price_incl_tax` as the price of the product variant.
|
||||
|
||||
:::note
|
||||
|
||||
You must pass one of the [pricing parameters](#product-pricing-parameters) to the request to retrieve these values. Otherwise, their value will be `null`.
|
||||
|
||||
:::
|
||||
|
||||
Prices in Medusa are stored as the currency's smallest unit. So, for currencies that are not zero-decimal, the amount is stored multiplied by a `100`. You can learn more about this in the [Product conceptual guide](../products.md#storing-the-product-variants-price).
|
||||
|
||||
So, to show the correct price, you would need to convert it to its actual price with a method like this:
|
||||
|
||||
```ts
|
||||
const convertToDecimal = (amount: number) => {
|
||||
return Math.floor(amount) / 100
|
||||
}
|
||||
```
|
||||
|
||||
<!-- vale docs.Spacing = NO -->
|
||||
|
||||
To display it along with a currency, it’s recommended to use JavaScript’s [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). For example:
|
||||
|
||||
<!-- vale docs.Spacing = YES -->
|
||||
|
||||
```ts
|
||||
new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "eur",
|
||||
}).format(convertToDecimal(amount))
|
||||
```
|
||||
|
||||
Ideally, you would retrieve the value of the `currency` property from the selected region’s `currency_code` attribute.
|
||||
|
||||
Medusa React provides utility methods such as `formatVariantPrice` that handles this logic for you.
|
||||
|
||||
Here’s an example of how you can calculate the price with and without Medusa React:
|
||||
|
||||
<Tabs groupId="client-preference" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Without Medusa Client" default>
|
||||
|
||||
```tsx
|
||||
import React, { useEffect, useState } from "react"
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
|
||||
const medusa = new Medusa({
|
||||
baseUrl: "<YOUR_BACKEND_URL>",
|
||||
maxRetries: 3,
|
||||
})
|
||||
|
||||
function Products() {
|
||||
const [products, setProducts] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
medusa.products.list({
|
||||
// TODO assuming region is already defined somewhere
|
||||
region_id: region.id,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
// ignore pagination for sake of example
|
||||
setProducts(products)
|
||||
})
|
||||
})
|
||||
|
||||
const convertToDecimal = (amount) => {
|
||||
return Math.floor(amount) / 100
|
||||
}
|
||||
|
||||
const formatPrice = (amount) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
// TODO assuming region is already defined somewhere
|
||||
currency: region.currency_code,
|
||||
}).format(convertToDecimal(amount))
|
||||
}
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<>
|
||||
{product.variants.map((variant) => (
|
||||
<li key={variant.id}>{
|
||||
formatPrice(variant.calculated_price_incl_tax)
|
||||
}</li>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="With Medusa React">
|
||||
|
||||
```tsx
|
||||
import { formatVariantPrice, useProducts } from "medusa-react"
|
||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
region_id: region.id, // assuming already defined somewhere
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && (
|
||||
<span>No Products</span>
|
||||
)}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<>
|
||||
{product.variants.map(
|
||||
(variant: ProductVariant) => (
|
||||
<li key={variant.id}>
|
||||
{formatVariantPrice({
|
||||
variant,
|
||||
// assuming already defined somewhere
|
||||
region,
|
||||
})}
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Search Products
|
||||
|
||||
:::note
|
||||
|
||||
The Search functionality requires either installing a [search plugin](../../../plugins/search/index.mdx) or creating a search service.
|
||||
|
||||
:::
|
||||
|
||||
You can search products using the [Search Products endpoint](https://docs.medusajs.com/api/store#tag/Products/operation/PostProductsSearch):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.search({
|
||||
q: "Shirt",
|
||||
})
|
||||
.then(({ hits }) => {
|
||||
console.log(hits.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products/search?q=Shirt`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ hits }) => {
|
||||
console.log(hits.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/store/products/search?q=Shirt'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the query parameter `q` being the term to search products for. The search plugin or service you’re using determine how `q` will be used to search the products. It also accepts pagination parameters as explained in the [API reference](https://docs.medusajs.com/api/store#products_postproductssearch).
|
||||
|
||||
The request returns a `hits` array holding the result items. The structure of the items depends on the plugin you’re using.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Product by ID
|
||||
|
||||
You can retrieve the details of a single product by its ID using the [Get a Product endpoint](https://docs.medusajs.com/api/store#products_getproductsproduct):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.retrieve(productId)
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProduct } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { product, isLoading } = useProduct(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product && <span>{product.title}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)} attributes={{
|
||||
badge: true
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const products = await productService.list({
|
||||
id: productId,
|
||||
})
|
||||
|
||||
console.log(products[0])
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products/${productId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products/<PRODUCT_ID>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the product’s ID to be passed as a path parameter. You can also pass query parameters such as `cart_id` and `region_id` which are relevant for pricing as explained in the [Product Pricing Parameters section](#product-pricing-parameters). You can check the full list of accepted parameters in the [API reference](https://docs.medusajs.com/api/store#products_getproductsproduct).
|
||||
|
||||
The request returns a product object. You can display its price as explained in the [Display Product Price](#display-product-price) section.
|
||||
|
||||
You can also retrieve the product's categories by passing the `expand` query parameter similar to the explanation in [this section](#expand-categories).
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Product by Handle
|
||||
|
||||
On the storefront, you may use the handle of a product as its page’s path. For example, instead of displaying the product’s details on the path `/products/prod_123`, you can display it on the path `/products/shirt`, where `shirt` is the handle of the product. This type of URL is human-readable and is good for Search Engine Optimization (SEO)
|
||||
|
||||
You can retrieve the details of a product by its handle by sending a request to the List Products endpoint, passing the `handle` as a filter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
handle,
|
||||
})
|
||||
.then(({ products }) => {
|
||||
if (!products.length) {
|
||||
// product does not exist
|
||||
}
|
||||
const product = products[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
handle,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && (
|
||||
<span>Product does not exist</span>
|
||||
)}
|
||||
{products && products.length > 0 && products[0].title}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)} attributes={{
|
||||
badge: true
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const products = await productService.list({
|
||||
handle,
|
||||
})
|
||||
|
||||
console.log(products[0])
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?handle=${handle}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products }) => {
|
||||
if (!products.length) {
|
||||
// product does not exist
|
||||
}
|
||||
const product = products[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?handle=<HANDLE>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
As the `handle` of each product is unique, when you pass the handle as a filter you’ll either:
|
||||
|
||||
- receive an empty `products` array, meaning the product doesn’t exist;
|
||||
- or you’ll receive a `products` array with one item being the product you’re looking for. In this case, you can access the product at index `0`.
|
||||
|
||||
As explained earlier, make sure to pass the [product pricing parameters](#product-pricing-parameters) to [display the product's price](#display-product-price).
|
||||
|
||||
You can also retrieve the product's categories by passing the `expand` query parameter as explained in [the Expand Categories section](#expand-categories).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to use regions in a storefront](../../regions-and-currencies/storefront/use-regions.mdx)
|
||||
- [How to implement cart functionality](../../carts-and-checkout/storefront/implement-cart.mdx)
|
||||
@@ -0,0 +1,370 @@
|
||||
---
|
||||
description: 'Learn how to use product categories in your storefront using the REST APIs. This includes listing categories and retrieving a single category.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Use Product Categories in a Storefront
|
||||
|
||||
In this document, you’ll learn how to use product categories in a storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the product category store REST APIs, you can list categories in your storefront.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following store functionalities:
|
||||
|
||||
- List and filter categories.
|
||||
- Retrieve a single category.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 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.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### @medusajs/product Module
|
||||
|
||||
This guide also includes code snippets to utilize the `@medusajs/product` module in your storefront, among other methods.
|
||||
|
||||
If you follow the `@medusajs/product` code blocks, it's assumed you already have the [@medusajs/product](../serverless-module.md) installed.
|
||||
|
||||
---
|
||||
|
||||
## List Categories
|
||||
|
||||
You can list product categories by sending a request to the [List Product Categories endpoint](https://docs.medusajs.com/api/store#product-categories_getproductcategories):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.productCategories.list()
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
console.log(product_categories.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProductCategories } from "medusa-react"
|
||||
import { ProductCategory } from "@medusajs/medusa"
|
||||
|
||||
function Categories() {
|
||||
const {
|
||||
product_categories,
|
||||
isLoading,
|
||||
} = useProductCategories()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product_categories && !product_categories.length && (
|
||||
<span>No Categories</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="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const categories = await productService.listCategories()
|
||||
|
||||
console.log(categories)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/product-categories`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
console.log(product_categories.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/product-categories' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request does not require any query parameters. You can, however, pass query parameters to filter the list of categories, such as the passing the `q` query parameter to search categories by title or handle. You can learn about available query parameters in the [API reference](https://docs.medusajs.com/api/store#product-categories_getproductcategories).
|
||||
|
||||
The request returns an array of product categories along with [pagination fields](https://docs.medusajs.com/api/store#pagination).
|
||||
|
||||
### Including all Nested Categories
|
||||
|
||||
By default, the categories are not retrieved along with their nested children. To retrieve categories with their nested children, make sure to pass the query parameter `include_descendants_tree` with the value `true`. Nested categories will be available inside each category object under the property `category_children`, which is an array of categories.
|
||||
|
||||
---
|
||||
|
||||
## Get a Category by ID
|
||||
|
||||
You can retrieve a single product category by its ID using the [Get a Product Category endpoint](https://docs.medusajs.com/api/store#product-categories_getproductcategoriescategory):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.productCategories.retrieve(productCategoryId)
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.name)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProductCategory } from "medusa-react"
|
||||
|
||||
function Category() {
|
||||
const { product_category, isLoading } = useProductCategory()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product_category && <span>{product_category.name}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Category
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const categories = await productService.listCategories({
|
||||
id: productCategoryId,
|
||||
})
|
||||
|
||||
console.log(categories[0])
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/product-categories/${productCategoryId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.name)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/product-categories/<CAT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the product category ID as a path parameter. You can also pass query parameters such as `expand` and `fields` as explained in the [API reference](https://docs.medusajs.com/api/store#product-categories_getproductcategoriescategory).
|
||||
|
||||
The request returns the category as an object.
|
||||
|
||||
---
|
||||
|
||||
## Get a Category by Its Handle
|
||||
|
||||
On the storefront, you may use the handle of a category as its page’s path. For example, instead of displaying the category's details on the path `/categories/pcat_123`, you can display it on the path `/categories/women`, where `women` is the handle of the category. This type of URL is human-readable and is good for Search Engine Optimization (SEO)
|
||||
|
||||
You can retrieve the details of a category by its handle by sending a request to the List Categories endpoint, passing the `handle` as a filter:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.productCategories.list({
|
||||
handle: "women",
|
||||
})
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
if (!product_categories.length) {
|
||||
// category does not exist
|
||||
}
|
||||
const category = product_categories[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProductCategories } from "medusa-react"
|
||||
import { ProductCategory } from "@medusajs/medusa"
|
||||
|
||||
function Categories() {
|
||||
const {
|
||||
product_categories,
|
||||
isLoading,
|
||||
} = useProductCategories({
|
||||
handle: "women",
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product_categories && !product_categories.length && (
|
||||
<span>No Categories</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="module" label={(
|
||||
<>
|
||||
@medusajs/product
|
||||
<Badge variant="orange-dark">beta</Badge>
|
||||
</>
|
||||
)}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
// in an async function, or you can use promises
|
||||
async () => {
|
||||
// ...
|
||||
const productService = await initializeProductModule()
|
||||
const categories = await productService.listCategories({
|
||||
handle,
|
||||
})
|
||||
|
||||
console.log(categories[0])
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/product-categories?handle=women`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
if (!product_categories.length) {
|
||||
// category does not exist
|
||||
}
|
||||
const category = product_categories[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/product-categories?handle=women' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
As the `handle` of each category is unique, when you pass the handle as a filter you’ll either:
|
||||
|
||||
- receive an empty `product_categories` array, meaning the category doesn’t exist;
|
||||
- or you’ll receive a `product_categories` array with one item being the category you’re looking for. In this case, you can access the category at index `0`. You can then display the product category as expected.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage product categories](../admin/manage-categories.mdx).
|
||||
@@ -0,0 +1,435 @@
|
||||
---
|
||||
description: 'Learn how to manage currencies using the Admin APIs. This includes how to list and update currencies, and how to manage currencies in a store.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Currencies Using Admin APIs
|
||||
|
||||
In this document, you’ll learn how to manage currencies using the Admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
The currencies admin APIs allow you to manage currencies in your commerce store.
|
||||
|
||||
Currencies are available in your commerce system, even if they’re not added to the store. This guide explains how to manage currencies globally, and how to manage them from a store’s context.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List currencies
|
||||
- Update a currency
|
||||
- Manage currencies in a store including listing, adding, and removing currencies from a store.
|
||||
|
||||
:::note
|
||||
|
||||
As explained in the [Currencies architecture guide](../currencies.md), currencies are created through migrations. So, you can’t create a currency through the admin APIs.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Currencies
|
||||
|
||||
You can list all available currencies in your system by sending a request to the [List Currencies endpoint](https://docs.medusajs.com/api/admin#currencies_getcurrencies):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.currencies.list()
|
||||
.then(({ currencies, count, offset, limit }) => {
|
||||
console.log(currencies.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Currency } from "@medusajs/medusa"
|
||||
import { useAdminCurrencies } from "medusa-react"
|
||||
|
||||
const Currencies = () => {
|
||||
const { currencies, isLoading } = useAdminCurrencies()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{currencies && !currencies.length && (
|
||||
<span>No Currencies</span>
|
||||
)}
|
||||
{currencies && currencies.length > 0 && (
|
||||
<ul>
|
||||
{currencies.map((currency: Currency) => (
|
||||
<li key={currency.code}>{currency.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Currencies
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/currencies`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ currencies, count, offset, limit }) => {
|
||||
console.log(currencies.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/currencies' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint accepts optional filter and search query parameters. For example, you can pass the `code` query parameter to search the list of currencies by a code. You can learn about available query parameters in the [API reference](https://docs.medusajs.com/api/admin#currencies_getcurrencies).
|
||||
|
||||
This request returns an array of currencies along with pagination parameters.
|
||||
|
||||
---
|
||||
|
||||
## Update Currencies
|
||||
|
||||
You can update a currency by sending a request to the [Update Currency endpoint](https://docs.medusajs.com/api/admin#currencies_postcurrenciescurrency):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.currencies.update(code, {
|
||||
includes_tax: true,
|
||||
})
|
||||
.then(({ currency }) => {
|
||||
console.log(currency.code)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateCurrency } from "medusa-react"
|
||||
|
||||
const UpdateCode = () => {
|
||||
const updateCode = useAdminUpdateCurrency(code)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateCode.mutate({
|
||||
includes_tax,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateCode
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/currencies/${code}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
includes_tax,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ currency }) => {
|
||||
console.log(currency.code)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/currencies/<CODE>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"includes_tax": true
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the currency code as a path parameter.
|
||||
|
||||
In the request body parameter, it accepts the field `includes_tax` which is a boolean value that indicates whether tax inclusion is enabled for the currency. This is only available if you’ve enabled the Tax Inclusive feature.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more in the [Tax-Inclusive Pricing guide](../../taxes/inclusive-pricing.md).
|
||||
|
||||
:::
|
||||
|
||||
The request returns the updated currency as an object.
|
||||
|
||||
---
|
||||
|
||||
## Manage Currencies in a Store
|
||||
|
||||
This section explains how you can manage the currencies of a store.
|
||||
|
||||
### List Currencies in a Store
|
||||
|
||||
You can list currencies in a store by sending a request to the [Get Store Details endpoint](https://docs.medusajs.com/api/admin#store_getstore):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.store.retrieve()
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies, store.default_currency)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Currency } from "@medusajs/medusa"
|
||||
import { useAdminStore } from "medusa-react"
|
||||
|
||||
const StoreCurrencies = () => {
|
||||
const { store, isLoading } = useAdminStore()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{store && store.default_currency && (
|
||||
<span>
|
||||
Default Currency: {store.default_currency.code}
|
||||
</span>
|
||||
)}
|
||||
{store && !store.currencies.length && (
|
||||
<span>No Currencies</span>
|
||||
)}
|
||||
{store?.currencies && store.currencies.length > 0 && (
|
||||
<ul>
|
||||
{store.currencies.map((currency: Currency) => (
|
||||
<li key={currency.code}>{currency.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StoreCurrencies
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/store`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies, store.default_currency)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/store' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the store as an object. In that object, there are two properties related to currencies:
|
||||
|
||||
1. `default_currency` is a currency object with details about the default currency.
|
||||
2. `currencies` is an array of currency objects.
|
||||
|
||||
### Add Currency to the Store
|
||||
|
||||
You can add a currency to a store using the [Add a Currency Code endpoint](https://docs.medusajs.com/api/admin#store_poststorecurrenciescode):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.store.addCurrency(code)
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminAddStoreCurrency } from "medusa-react"
|
||||
|
||||
const AddCurrency = () => {
|
||||
const addCode = useAdminAddStoreCurrency()
|
||||
// ...
|
||||
|
||||
const handleAdd = () => {
|
||||
addCode.mutate(code)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default AddCurrency
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/store/currencies/${code}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/store/currencies/<CODE>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the currency code to be passed as a path parameter.
|
||||
|
||||
The request returns the updated store as an object. You can access the new list of currencies under the `store.currencies` property.
|
||||
|
||||
### Delete a Currency from the Store
|
||||
|
||||
You can remove a currency from a store by sending a request to the [Delete Currency Code endpoint](https://docs.medusajs.com/api/admin#store_deletestorecurrenciescode):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.store.deleteCurrency(code)
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteStoreCurrency } from "medusa-react"
|
||||
|
||||
const DeleteCurrency = () => {
|
||||
const deleteCurrency = useAdminDeleteStoreCurrency()
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteCurrency.mutate(code)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteCurrency
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/store/currencies/${code}`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ store }) => {
|
||||
console.log(store.currencies)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/store/currencies/<CODE>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the currency code to be passed as a path parameter.
|
||||
|
||||
The request returns the updated store as an object. You can access the new list of currencies under the `store.currencies` property.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Currency architecture](../currencies.md)
|
||||
- [How to manage regions](./manage-regions.mdx)
|
||||
@@ -0,0 +1,556 @@
|
||||
---
|
||||
description: 'Learn how to implement region-related functionalities for admins using the REST APIs. This includes how to list regions, and create, update, and delete regions.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Regions using Admin APIs
|
||||
|
||||
In this document, you’ll learn how to manage regions using the Admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the [region admin REST APIs](https://docs.medusajs.com/api/api/admin#regions), you can manage regions in your store, including creating, updating, and deleting regions.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List regions
|
||||
- Create a region
|
||||
- Update a region
|
||||
- Add shipping options to a region
|
||||
- Delete a Region
|
||||
|
||||
:::info
|
||||
|
||||
You can use Medusa’s Admin APIs to achieve more functionalities as well. Check out the [API reference](https://docs.medusajs.com/api/admin#regions) to learn more.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Regions
|
||||
|
||||
You can retrieve regions available on your backend using the [List Regions](https://docs.medusajs.com/api/admin#regions_getregions) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.list()
|
||||
.then(({ regions, limit, offset, count }) => {
|
||||
console.log(regions.length)
|
||||
// display regions
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import { useAdminRegions } from "medusa-react"
|
||||
|
||||
const Regions = () => {
|
||||
const { regions, isLoading } = useAdminRegions()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{regions && !regions.length && <span>No Regions</span>}
|
||||
{regions && regions.length > 0 && (
|
||||
<ul>
|
||||
{regions.map((region: Region) => (
|
||||
<li key={region.id}>{region.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Regions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ regions, limit, offset, count }) => {
|
||||
console.log(regions.length)
|
||||
// display regions
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/regions' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of regions, as well as [pagination fields](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
You can also pass filters and other selection query parameters to the request. Check out the [API reference](https://docs.medusajs.com/api/admin#regions_getregions) for more details on available query parameters.
|
||||
|
||||
---
|
||||
|
||||
## Create a Region
|
||||
|
||||
You can create a region by sending a request to the [Create a Region](https://docs.medusajs.com/api/admin#regions_postregions) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.create({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
tax_rate: 0,
|
||||
payment_providers: [
|
||||
"manual",
|
||||
],
|
||||
fulfillment_providers: [
|
||||
"manual",
|
||||
],
|
||||
countries: [
|
||||
"DK",
|
||||
],
|
||||
})
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateRegion } from "medusa-react"
|
||||
|
||||
const CreateRegion = () => {
|
||||
const createRegion = useAdminCreateRegion()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createRegion.mutate({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
tax_rate: 0,
|
||||
payment_providers: [
|
||||
"manual",
|
||||
],
|
||||
fulfillment_providers: [
|
||||
"manual",
|
||||
],
|
||||
countries: [
|
||||
"DK",
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateRegion
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
tax_rate: 0,
|
||||
payment_providers: [
|
||||
"manual",
|
||||
],
|
||||
fulfillment_providers: [
|
||||
"manual",
|
||||
],
|
||||
countries: [
|
||||
"DK",
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/regions' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Europe",
|
||||
"currency_code": "eur",
|
||||
"tax_rate": 0,
|
||||
"payment_providers": [
|
||||
"manual"
|
||||
],
|
||||
"fulfillment_providers": [
|
||||
"manual"
|
||||
],
|
||||
"countries": [
|
||||
"DK"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
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 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.
|
||||
|
||||
This request also accepts optional parameters, which you can view in the [API reference](https://docs.medusajs.com/api/admin#regions_postregions).
|
||||
|
||||
The request returns the created region in the response.
|
||||
|
||||
---
|
||||
|
||||
## Update a Region
|
||||
|
||||
You can update any of the region’s fields and configurations. The REST APIs offer different APIs for updating specific configurations, such as the [Add Country](https://docs.medusajs.com/api/admin#regions_postregionsregioncountries) endpoint.
|
||||
|
||||
Alternatively, you can update the details of a region using the [Update a Region](https://docs.medusajs.com/api/admin#regions_postregionsregion) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.update(regionId, {
|
||||
countries: [
|
||||
"DK",
|
||||
"DE",
|
||||
],
|
||||
})
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateRegion } from "medusa-react"
|
||||
|
||||
const UpdateRegion = () => {
|
||||
const updateRegion = useAdminUpdateRegion(regionId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateRegion.mutate({
|
||||
countries: [
|
||||
"DK",
|
||||
"DE",
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateRegion
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions/${regionId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
countries: [
|
||||
"DK",
|
||||
"DE",
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/regions/<REGION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"countries": [
|
||||
"DK",
|
||||
"DE"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts in its body parameters any of the region’s fields that you want to update. In the example above, you update the list of countries that are included in that region.
|
||||
|
||||
You can see the list of accepted fields in the [API reference](https://docs.medusajs.com/api/admin#regions_postregionsregion).
|
||||
|
||||
This request returns the full object of the updated region.
|
||||
|
||||
:::tip
|
||||
|
||||
In the example above, the list of countries replace any countries that were previously in the region. So, if you’re adding a country, make sure to include previously added countries as well.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Add a Shipping Option to a Region
|
||||
|
||||
You can add a shipping option to a region by sending a request to the [Create Shipping Option](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.shippingOptions.create({
|
||||
name: "PostFake",
|
||||
region_id: regionId,
|
||||
provider_id: "manual",
|
||||
data: {
|
||||
},
|
||||
price_type: "flat_rate",
|
||||
amount: 1000,
|
||||
})
|
||||
.then(({ shipping_option }) => {
|
||||
console.log(shipping_option.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateShippingOption } from "medusa-react"
|
||||
|
||||
const Region = () => {
|
||||
const createShippingOption = useAdminCreateShippingOption()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createShippingOption.mutate({
|
||||
name: "PostFake",
|
||||
region_id: regionId,
|
||||
provider_id: "manual",
|
||||
data: {
|
||||
},
|
||||
price_type: "flat_rate",
|
||||
amount: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Region
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/shipping-options`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "PostFake",
|
||||
region_id: regionId,
|
||||
provider_id: "manual",
|
||||
price_type: "flat_rate",
|
||||
data: {
|
||||
},
|
||||
amount: 1000,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ shipping_option }) => {
|
||||
console.log(shipping_option.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/shipping-options' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "PostFake",
|
||||
"region_id": "<REGION_ID>",
|
||||
"provider_id": "manual",
|
||||
"price_type": "flat_rate",
|
||||
"data": {},
|
||||
"amount": 1000
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the following body parameters:
|
||||
|
||||
- `name`: The name of the shipping option.
|
||||
- `region_id`: The ID of the region.
|
||||
- `provider_id`: The ID of the fulfillment provider. The fulfillment provider must be enabled in the region.
|
||||
- `data`: An object of data needed for the fulfillment provider to handle shipping with this shipping option. If none is required, you can pass an empty object.
|
||||
- `price_type`: The price type of the shipping option. Can be `flat_rate` for fixed price, or `calculated` for prices that are calculated using custom logic.
|
||||
- If `price_type` is `flat_rate`, the `amount` field is then required. It is the price of the shipping option. If `price_type` is `calculated`, `amount` is not required.
|
||||
|
||||
The `is_return` body parameter can also be passed if the shipping option is a return shipping option. Its boolean value indicates whether the shipping option is a return option or not.
|
||||
|
||||
This request accepts other optional body parameters, which you can learn more about in the [API reference](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions).
|
||||
|
||||
This request returns the created shipping option.
|
||||
|
||||
:::tip
|
||||
|
||||
You can also manage shipping options such as list, update, and delete. You can learn more in the [API reference](https://docs.medusajs.com/api/admin#shipping-options_getshippingoptions).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Delete a Region
|
||||
|
||||
You can delete a region by sending a request to the [Delete a Region](https://docs.medusajs.com/api/admin#regions_deleteregionsregion) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.delete(regionId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteRegion } from "medusa-react"
|
||||
|
||||
const Region = () => {
|
||||
const deleteRegion = useAdminDeleteRegion(regionId)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteRegion.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Region
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions/${regionId}`, {
|
||||
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/regions/<REGION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the region ID as a path parameter. It deletes the region and returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted region.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `region`.
|
||||
- `deleted`: A boolean value indicating whether the region was deleted.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use regions on the storefront](../storefront/use-regions.mdx)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
description: 'Learn about the Currency entity and its relation to other entities. Currencies are used to define the price of products, services, and other monetary details in a commerce system.'
|
||||
---
|
||||
|
||||
# Currency Architecture
|
||||
|
||||
In this document, you’ll learn about the Currency entity and its relation to other entities.
|
||||
|
||||
## Introduction
|
||||
|
||||
Currencies are used to define the price of products, services, and other monetary details in a commerce system.
|
||||
|
||||
Medusa supports multi-currency within your commerce store. You can specify prices per currency, and even per region. This means that you can serve customers globally without limitations related to pricing.
|
||||
|
||||
---
|
||||
|
||||
## Currency Entity Overview
|
||||
|
||||
The `Currency` entity has the following attributes:
|
||||
|
||||
- `code`: A string indicating the [3 character ISO code](https://en.wikipedia.org/wiki/ISO_4217#Active_codes) for the currency. This attribute also acts as a unique and primary column of the entity.
|
||||
- `symbol`: A string indicating the display symbol of the currency. This would typically be the symbol you would show to the customer or admin users when displaying a price.
|
||||
- `symbol_native`: A string indicating the native symbol of the currency.
|
||||
- `name`: A string indicating the name of the currency.
|
||||
|
||||
---
|
||||
|
||||
## How Currencies are Created
|
||||
|
||||
Currencies are defined in the core of your Medusa backend under `utils/currencies.js` (or `packages/medusa/src/utils/currencies.ts` if you’re using the Medusa mono-repository). They’re defined in an object in that file.
|
||||
|
||||
When you run the `migration` or `seed` command the first time on your Medusa server, a migration uses this object to insert all its properties (the currencies) into the database.
|
||||
|
||||
So, if you want to add more currencies, you can create a migration that inserts your currencies into the database.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about Migrations in [this guide](../../development/entities/migrations/overview.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
### Store
|
||||
|
||||
A store has a default currency and can have many currencies. These currencies are then used in other relations, such as when associating a region with a currency.
|
||||
|
||||
There are two relations available on the `Store` entity related to the `Currency` entity:
|
||||
|
||||
- You can expand the `default_currency` relation on a store and access the default currency with `store.default_currency`. You can also use the `default_currency_code` attribute on the store to access the code of the default currency.
|
||||
- You can expand the `currencies` relation on a store and access the currencies with `store.currencies`.
|
||||
|
||||
### Region
|
||||
|
||||
Each region is associated with a currency. A currency can be used in more than one region, but a region can have only one currency.
|
||||
|
||||
The relation is available on a region by expanding the `region.currency` relation and accessing `region.currency`. You can also access the currency code through the attribute `currency_code` on the region.
|
||||
|
||||
### MoneyAmount
|
||||
|
||||
The `MoneyAmount` entity is used to store the price of different entities within your commerce store.
|
||||
|
||||
The relation is available on the `MoneyAmount` entity by expanding the `currency` relation and accessing `money_amount.currency`. You can also access the currency code through the attribute `currency_code` on the money amount.
|
||||
|
||||
### Order
|
||||
|
||||
The `Order` entity includes a relation to the currency, as it is the currency that the order was placed in.
|
||||
|
||||
The relation is available on the `Order` entity by expanding the `currency` relation and accessing `order.currency`. You can also access the currency code through the attribute `currency_code` on the order.
|
||||
|
||||
### Payment
|
||||
|
||||
The `Payment` entity is used to represent a payment of any kind, such as for orders. Each payment is associated with a currency.
|
||||
|
||||
The relation is available on the `Payment` entity by expanding the `currency` relation and accessing `payment.currency_code`. You can also access the currency code through the attribute `currency_code` on the payment.
|
||||
|
||||
### PaymentCollection
|
||||
|
||||
The `PaymentCollection` entity is used to represent a collection of payments. Each payment collection is associated with a currency.
|
||||
|
||||
The relation is available on the `PaymentCollection` entity by expanding the `currency` relation and accessing `payment_collection.currency_code`. You can also access the currency code through the attribute `currency_code` on the payment collection.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Price Selection Strategy](../price-lists/price-selection-strategy.md)
|
||||
- [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md)
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
description: "Regions and currencies are essential components in a Medusa ecommerce store. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Regions and Currencies
|
||||
|
||||
Regions are essential to your Medusa ecommerce store. They represent different countries or regions your ecommerce store serves customers in. This overview introduces the available features related to regions and currencies.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Regions user guide](../../user-guide/regions/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Multi-Region Support and Management
|
||||
|
||||
In Medusa, you can create an unlimited number of regions. A region can be associated with one or more country.
|
||||
|
||||
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 processors, tax providers, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/admin/manage-regions',
|
||||
label: 'Admin: Manage Regions',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage regions using the Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/regions/manage',
|
||||
label: 'User Guide: Manage Regions',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to view and manage regions in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Multi-Currency Support
|
||||
|
||||
A currency can be associated with one or more regions. When setting prices for products, you can specify a price per currency and per region.
|
||||
|
||||
You have complete control over the format and value of the price of a product per currency and per region. This flexibility removes the restriction of being tied to a predefined conversion rate, which often leads to prices badly-formatted on the storefront.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/admin/manage-currencies',
|
||||
label: 'Admin: Manage Currencies',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage currencies using the Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/settings/currencies',
|
||||
label: 'User Guide: Manage Currencies',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage available currencies in Medusa Admin'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Region Switcher in the Storefront
|
||||
|
||||
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 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={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/storefront/use-regions',
|
||||
label: 'Storefront: Use Regions',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to display and use Regions in the storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#regions',
|
||||
label: 'Store APIs: Regions',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Regions.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how Regions and Currencies are built, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/regions',
|
||||
label: 'Architecture: Region',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Region Architecture.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/currencies',
|
||||
label: 'Architecture: Currency',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Currency Architecture.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Regions and Currencies' relation to other modules in Medusa.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/overview',
|
||||
label: 'Taxes',
|
||||
customProps: {
|
||||
icon: Icons['cash-solid'],
|
||||
description: 'Manage tax providers per region. Integrate third-party tax providers.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Carts and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Show a personalized checkout experience based on the region selected.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
description: 'Learn about what Regions are and how they are used in a Medusa backend. Regions represent at least one country on the Medusa backend.'
|
||||
---
|
||||
|
||||
# Regions Architecture
|
||||
|
||||
In this document, you’ll learn about the Region entity and its architecture.
|
||||
|
||||
## Introduction
|
||||
|
||||
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 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.
|
||||
|
||||
---
|
||||
|
||||
## Region Configurations
|
||||
|
||||
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 processors.
|
||||
5. The enabled fulfillment providers.
|
||||
6. The shipping and return shipping options.
|
||||
|
||||
---
|
||||
|
||||
## One vs Multiple Regions
|
||||
|
||||
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 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 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.
|
||||
|
||||
---
|
||||
|
||||
## Region Entity Overview
|
||||
|
||||
A region is stored in the database as a [Region](../../references/entities/classes/Region.md) entity. Some of its important attributes are:
|
||||
|
||||
- `name`: The name of the region. Customers will see this name on the storefront.
|
||||
- `tax_rate`: A number that indicates the tax rate. The tax rate is a percentage.
|
||||
- `tax_code`: An optional string that is used as the code for the default tax rate.
|
||||
- `gift_cards_taxable`: A boolean value that indicates whether gift cards in a region are taxable or not.
|
||||
- `automatic_taxes`: A boolean value that indicates whether taxes should be calculated during checkout automatically or manually for that region. You can learn more about manually calculating taxes in [this documentation](../taxes/storefront/manual-calculation.md).
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
As regions are a core part of your Medusa backend, there are many relations to other entities.
|
||||
|
||||
This section covers relations to entities that make up the configurations of a region.
|
||||
|
||||

|
||||
|
||||
### Country
|
||||
|
||||
A region must have at least one country. A country can belong to only one region.
|
||||
|
||||
The relation between the `Region` and `Country` entities is available on both entities:
|
||||
|
||||
- You can access the countries that belong to a region by expanding the `countries` relation and accessing `region.countries`.
|
||||
- You can access the region of a country by expanding the `region` relation and accessing `country.region`. Also, you can access the ID of the region through `country.region_id`.
|
||||
|
||||
### Currency
|
||||
|
||||
A region must have one currency. A currency can be used for more than one region.
|
||||
|
||||
The relation is available on a region by expanding the `currency` relation and accessing `region.currency`. The 3 character currency code can also be accessed through `region.currency_code`.
|
||||
|
||||
### FulfillmentProvider
|
||||
|
||||
A region must have at least one fulfillment provider. A fulfillment provider can be used in more than one region.
|
||||
|
||||
The relation is available on a region by expanding the `fulfillment_providers` relation and accessing `region.fulfillment_providers`.
|
||||
|
||||
### PaymentProvider
|
||||
|
||||
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`.
|
||||
|
||||
### ShippingOption
|
||||
|
||||
:::info
|
||||
|
||||
Both shipping options and return shipping options are represented by the `ShippingOption` entity. You can learn more in the [Shipping Architecture documentation](../carts-and-checkout/shipping.md#shipping-option).
|
||||
|
||||
:::
|
||||
|
||||
More than one shipping option can belong to a region. The relation is available on a shipping option by expanding the `region` relation and accessing `shipping_option.region`.
|
||||
|
||||
### TaxProvider
|
||||
|
||||
A region can have one tax provider. A tax provider can be used for more than one region.
|
||||
|
||||
The relation is available on a region by expanding the `tax_provider` relation and accessing `region.tax_provider`. You can also access the ID of the tax provider through `region.tax_provider_id`.
|
||||
|
||||
### TaxRate
|
||||
|
||||
A region can have more than one tax rate, and a tax rate belongs to one region.
|
||||
|
||||
The relation between the `Region` and `TaxRate` entities is available on both entities:
|
||||
|
||||
- You can access the tax rates of a region by expanding the `tax_rates` relation and accessing `region.tax_rates`.
|
||||
- You can access the region of a tax rate by expanding the `region` relation and accessing `tax_rate.region`. You can also access the ID of the region through `tax_rate.region_id`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use regions in a storefront](./storefront/use-regions.mdx)
|
||||
- [Use regions in the admin](./admin/manage-regions.mdx)
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: 'Learn how to implement region-related functionalities on your storefront using the REST APIs. This includes showing the customer available regions, showing product prices, and setting the region of a cart.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Use Regions in the Storefront
|
||||
|
||||
In this document, you’ll learn how to use Regions in the storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
Regions represent the supported countries and currencies in your store. Customers can use the region that fits them based on their country and currency.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to implement the following in your storefront:
|
||||
|
||||
- Show customers available regions.
|
||||
- Show product prices based on the selected region.
|
||||
- Set a cart’s region to the selected region.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
---
|
||||
|
||||
## Show List of Regions
|
||||
|
||||
Customers should be able to see the list of available regions and select their region.
|
||||
|
||||
You can retrieve available regions by sending a request to the [List Regions](https://docs.medusajs.com/api/store#regions_getregions) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.regions.list()
|
||||
.then(({ regions }) => {
|
||||
console.log(regions.length)
|
||||
// show customers available regions
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import { useRegions } from "medusa-react"
|
||||
|
||||
const Regions = () => {
|
||||
const { regions, isLoading } = useRegions()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{regions?.length && (
|
||||
<ul>
|
||||
{regions.map((region: Region) => (
|
||||
<li key={region.id}>
|
||||
{region.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Regions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/regions`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ regions }) => {
|
||||
console.log(regions.length)
|
||||
// show customers available regions
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the list of available regions. You can show them to your customers to select their region.
|
||||
|
||||
When a customer selects a region, you should store that region’s ID. You’ll need it to show the customer product prices based on the selected region, and set the region of their cart.
|
||||
|
||||
---
|
||||
|
||||
## Show Product Prices Based on the Selected Region
|
||||
|
||||
To retrieve products with the prices based on the selected regions, you can pass the `region_id` query parameter to the [List Products](https://docs.medusajs.com/api/store#products_getproducts) or [Get a Product](https://docs.medusajs.com/api/store#products_getproductsproduct) endpoints.
|
||||
|
||||
For example:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
region_id: regionId,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
// show customer the products
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { formatVariantPrice } from "medusa-react"
|
||||
|
||||
const Product = () => {
|
||||
// retrieve the region and variant(s)
|
||||
// ...
|
||||
|
||||
return (
|
||||
<span>
|
||||
{formatVariantPrice({
|
||||
variant, // ProductVariant
|
||||
region, // Region
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default Product
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?region_id=${regionId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
// show customer the products
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this example, you send a request to the List Products endpoint, passing it the `region_id` query parameter. It is assumed that you have the ID of the region stored in the variable `regionId`.
|
||||
|
||||
This request returns the list of products along with [pagination fields](https://docs.medusajs.com/api/store#pagination). The prices of the products are based on the selected region.
|
||||
|
||||
---
|
||||
|
||||
## Set a Cart’s Region
|
||||
|
||||
When the customer changes their region, you must also reflect that change on their cart.
|
||||
|
||||
You can set the cart’s region while creating it and later on by updating it.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn how to implement cart functionalities in your storefront in [this documentation](../../carts-and-checkout/storefront/implement-cart.mdx).
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
region_id: regionId,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeRegionId = (region_id: string) => {
|
||||
updateCart.mutate({
|
||||
region_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
region_id: regionId,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this example, you send a request to the [Update Cart](https://docs.medusajs.com/api/store#carts_postcartscart) endpoint. In the request’s body, you pass the parameter `region_id` and set its value to the selected region’s ID. It is assumed that you have the ID of the region stored in the variable `regionId`.
|
||||
|
||||
This request returns the full object of the updated cart.
|
||||
@@ -0,0 +1,799 @@
|
||||
---
|
||||
description: 'Learn how to implement sales channels features for admins in Medusa using the REST APIs. This includes how to list sales channels, and create, retrieve, update and delete a sales channel. This also includes how to manage products in a sales channel.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Sales Channels
|
||||
|
||||
In this document, you’ll learn how to manage sales channels and their products and orders using the Admin APIs.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re looking to learn more in-depth about what Sales Channels are and how they work, check out [this documentation](../sales-channels.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Using Medusa’s Admin APIs, you can manage Sales Channels including creating, retrieving, updating, and deleting sales channels. You can also manage their products and orders.
|
||||
|
||||
This guide explains how to perform all these operations using the Admin APIs.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Authenticated Admin User
|
||||
|
||||
You must be an authenticated admin user before following along with the steps in this guide.
|
||||
|
||||
You can learn more about [authenticating as an admin user in the API reference](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## Create Sales Channels
|
||||
|
||||
You can create a sales channel by sending a request to the Create a Sales Channel endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.create({
|
||||
name: "App",
|
||||
description: "Mobile app",
|
||||
})
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateSalesChannel } from "medusa-react"
|
||||
|
||||
const CreateSalesChannel = () => {
|
||||
const createSalesChannel = useAdminCreateSalesChannel()
|
||||
// ...
|
||||
|
||||
const handleCreate = (name: string, description: string) => {
|
||||
createSalesChannel.mutate({
|
||||
name,
|
||||
description,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateSalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "App",
|
||||
description: "Mobile app",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/sales-channels' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
name: 'App',
|
||||
description: 'Mobile app'
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the request body parameters `name`, and optionally accepts the `description` and `is_disabled` request body parameters.
|
||||
|
||||
It returns the created sales channel.
|
||||
|
||||
---
|
||||
|
||||
## List Sales Channels
|
||||
|
||||
You can list all sales channels by sending a request to the List Sales Channels endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.list()
|
||||
.then(({ sales_channels, limit, offset, count }) => {
|
||||
console.log(sales_channels.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { SalesChannel } from "@medusajs/medusa"
|
||||
import { useAdminSalesChannels } from "medusa-react"
|
||||
|
||||
const SalesChannels = () => {
|
||||
const { sales_channels, isLoading } = useAdminSalesChannels()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{sales_channels && !sales_channels.length && (
|
||||
<span>No Sales Channels</span>
|
||||
)}
|
||||
{sales_channels && sales_channels.length > 0 && (
|
||||
<ul>
|
||||
{sales_channels.map((salesChannel: SalesChannel) => (
|
||||
<li key={salesChannel.id}>{salesChannel.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SalesChannels
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channels, limit, offset, count }) => {
|
||||
console.log(sales_channels.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/sales-channels' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of all sales channels in your store. You can also pass query parameters to filter or customize the pagination of the results. Check out [the API Reference for a full list of query parameters.](https://docs.medusajs.com/api/admin#sales-channels_getsaleschannels)
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Sales Channel
|
||||
|
||||
You can retrieve a sales channel’s details by its ID using the Get Sales Channel endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.retrieve(salesChannelId)
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminSalesChannel } from "medusa-react"
|
||||
|
||||
const SalesChannel = () => {
|
||||
const {
|
||||
sales_channel,
|
||||
isLoading,
|
||||
} = useAdminSalesChannel(salesChannelId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{sales_channel && <span>{sales_channel.name}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels/${salesChannelId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channels, limit, offset, count }) => {
|
||||
console.log(sales_channels.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/sales-channels/<SALES_CHANNEL_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns the sales channel with the specified ID.
|
||||
|
||||
---
|
||||
|
||||
## Update a Sales Channel
|
||||
|
||||
You can update a Sales Channel’s details and attributes by sending a request to the Update Sales Channel endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.update(salesChannelId, {
|
||||
is_disabled: false,
|
||||
})
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateSalesChannel } from "medusa-react"
|
||||
|
||||
const UpdateSalesChannel = () => {
|
||||
const updateSalesChannel = useAdminUpdateSalesChannel(
|
||||
salesChannelId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateSalesChannel.mutate({
|
||||
is_disabled: false,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateSalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels/${salesChannelId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
is_disabled: false,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/sales-channels/<SALES_CHANNEL_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"is_disabled": false
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this example, you enable a sales channel by changing the value of the `is_disabled` attribute.
|
||||
|
||||
This request returns the updated sales channel.
|
||||
|
||||
You can check out [the API Reference for a full list of body parameters](https://docs.medusajs.com/api/admin#sales-channels_postsaleschannelssaleschannel) that you can pass to this request.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Sales Channel
|
||||
|
||||
You can delete a sales channel by sending a request to the Delete Sales Channel endpoint with the ID of the sales channel to delete:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.delete(salesChannelId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteSalesChannel } from "medusa-react"
|
||||
|
||||
const SalesChannel = () => {
|
||||
const deleteSalesChannel = useAdminDeleteSalesChannel(
|
||||
salesChannelId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteSalesChannel.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default SalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/sales-channels/${salesChannelId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.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/sales-channels/<SALES_CHANNEL_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The ID of the deleted sales channel is returned in the response.
|
||||
|
||||
---
|
||||
|
||||
## Manage Products
|
||||
|
||||
### Add Product to a Sales Channel
|
||||
|
||||
To add a product to a sales channel, send a request to the Sales Channel’s Add Products endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.addProducts(salesChannelId, {
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminAddProductsToSalesChannel } from "medusa-react"
|
||||
|
||||
const SalesChannel = () => {
|
||||
const addProducts = useAdminAddProductsToSalesChannel(
|
||||
salesChannelId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAddProducts = (productId: string) => {
|
||||
addProducts.mutate({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default SalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/sales-channels/${salesChannelId}/products/batch`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/sales-channels/<SALES_CHANNEL_ID>/products/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"product_ids": [
|
||||
{
|
||||
"id": "<PRODUCT_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the `product_ids` body parameter, which is an array of objects. Each object in the array must have an `id` property with the ID of the product to add as a value.
|
||||
|
||||
This request returns the sales channel.
|
||||
|
||||
### List Products Available in a Sales Channel
|
||||
|
||||
You can list the products available in a sales channel by sending a request to the List Products endpoint and passing the `sales_channel_id` query parameter to filter by a specific sales channel:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.products.list({
|
||||
sales_channel_id: [
|
||||
salesChannelId,
|
||||
],
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import {
|
||||
PricedProduct,
|
||||
} from "@medusajs/medusa/dist/types/pricing"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
|
||||
const SalesChannelProducts = () => {
|
||||
const { products, isLoading } = useAdminProducts({
|
||||
sales_channel_id: [salesChannelId],
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map(
|
||||
(product: (Product | PricedProduct)) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
{products && !products.length && (
|
||||
<span>No Products</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SalesChannelProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/products?sales_channel_id[0]=${salesChannelId}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/products?sales_channel_id[0]=<SALES_CHANNEL_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request returns an array of products that are available in the specified sales channel.
|
||||
|
||||
### Delete Products from a Sales Channel
|
||||
|
||||
:::info
|
||||
|
||||
Deleting a product from a sales channel doesn't delete it completely. It only makes it unavailable in that sales channel.
|
||||
|
||||
:::
|
||||
|
||||
You can delete a product from a sales channel by sending a request to the Sales Channel’s Delete Products endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
medusa.admin.salesChannels.removeProducts(salesChannelId, {
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminDeleteProductsFromSalesChannel,
|
||||
} from "medusa-react"
|
||||
|
||||
const SalesChannel = () => {
|
||||
const deleteProducts = useAdminDeleteProductsFromSalesChannel(
|
||||
salesChannelId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDeleteProducts = (productId: string) => {
|
||||
deleteProducts.mutate({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default SalesChannel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/sales-channels/${salesChannelId}/products/batch`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channel }) => {
|
||||
console.log(sales_channel.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/sales-channels/<SALES_CHANNEL_ID>/products/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"product_ids": [
|
||||
{
|
||||
"id": "<PRODUCT_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the `product_ids` body parameter, which is an array of objects. Each object in the array must have an `id` property with the ID of the product to delete as a value.
|
||||
|
||||
This request returns the sales channel.
|
||||
|
||||
---
|
||||
|
||||
## List Orders by Sales Channels
|
||||
|
||||
You can filter orders by a specific sales channel by sending a request to the List Orders endpoint and passing the `sales_channel_id` query parameter to filter by a specific sales channel:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.orders.list({
|
||||
sales_channel_id: [
|
||||
salesChannelId,
|
||||
],
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
})
|
||||
.then(({ orders, limit, offset, count }) => {
|
||||
console.log(orders.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import { useAdminOrders } from "medusa-react"
|
||||
|
||||
const SalesChannelOrders = () => {
|
||||
const { orders, isLoading } = useAdminOrders({
|
||||
sales_channel_id: [salesChannelId],
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{orders && orders.length > 0 && (
|
||||
<ul>
|
||||
{orders.map((order: Order) => (
|
||||
<li key={order.id}>{order.display_id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{orders && !orders.length && <span>No Orders</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SalesChannelOrders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/orders?sales_channel_id[0]=${salesChannelId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ orders, limit, offset, count }) => {
|
||||
console.log(orders.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/orders?sales_channel_id[0]=<SALES_CHANNEL_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request returns an array of orders that are associated with the specified sales channel.
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
description: "Sales channels are different methods a business offers its products for purchase for the customers. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Sales Channels
|
||||
|
||||
Sales channels are different methods a business offers its products for purchase for the customers. This overview introduces the available features related to sales channels.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Sales Channels user guide](../../user-guide/sales-channels/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Sales Channel Management
|
||||
|
||||
Admins can manage sales channels, creating an unlimited number of them. They specify the products’ availability for each sales channel.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/admin/manage',
|
||||
label: 'Admin: Manage Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage sales channels using the Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/sales-channels/manage',
|
||||
label: 'User Guide: Manage Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage sales channels using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/sales-channels/products',
|
||||
label: 'User Guide: Manage Products',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage products in a sales channel using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Sales Channels for Each Platform
|
||||
|
||||
Developers can associate a platform with a sales channel. For example, associating a storefront with a web sales channel. Only products available in that sales channel will be shown, and all orders placed on that sales channel will be associated with it.
|
||||
|
||||
Admins can filter orders by a sales channel. This would allow them to handle orders coming from each sales channel differently.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/storefront/use-sales-channels',
|
||||
label: 'Storefront: Use Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use sales channels in a storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/admin/manage#list-orders-by-sales-channels',
|
||||
label: 'Admin: Orders by Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to filter orders by a sales channel.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how sales channel related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels',
|
||||
label: 'Architecture: Sales Channel',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Sales Channels architecture.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Sales Channel’s relation to other modules in Medusa.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Product',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Products availability can be changed for each sales channel.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/overview',
|
||||
label: 'Orders',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Orders can be associated and organized by a sales channel.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
description: 'Learn what Sales Channels are and how they can be used in Medusa. Sales Channels allow merchants to separate between the different channels products are sold in.'
|
||||
---
|
||||
|
||||
# Sales Channels
|
||||
|
||||
In this document, you’ll learn about Sales Channels and how they can be used in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Sales Channels allow you to separate between the different channels you sell products in. For example, you can have a sales channel for your website and another for your mobile apps.
|
||||
|
||||
Sales Channels are associated with stores, products, carts, and orders: A store has a default sales channel; you can define which products are available in a sales channel; you can specify the sales channel of a cart; you can filter orders by sales channel.
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
The sales channel feature can be used in a variety of use cases including:
|
||||
|
||||
- Implement a B2B Ecommerce Store.
|
||||
- Specifying different products for each channel you sell in.
|
||||
- Support Omnichannel in your ecommerce store.
|
||||
|
||||
---
|
||||
|
||||
## SalesChannel Entity Overview
|
||||
|
||||
A sales channel is stored in the database as a [SalesChannel](../../references/entities/classes/SalesChannel.md) entity. Some of its important attributes are:
|
||||
|
||||
- `name`: The name of the sales channel.
|
||||
- `is_disabled`: A boolean value indicating of the Sales Channel is disabled or not.
|
||||
|
||||
---
|
||||
|
||||
## Relations to Other Entities
|
||||
|
||||
### Store
|
||||
|
||||
A store has a default sales channel. When you first run your Medusa backend or seed your database, a default sales channel is created, if it doesn’t exist, and associated with the store.
|
||||
|
||||
The relation is implemented in the [Store](../../references/entities/classes/Store.md) entity. You can access the default sales channel of a store by expanding the `default_sales_channel` relation and using `store.default_sales_channel`. You can also access the ID of the default sales channel using `store.default_sales_channel_id`.
|
||||
|
||||
### Product
|
||||
|
||||
Products can be available in more than one sales channel. You can then filter products by a sales channel using the Storefront and Admin APIs.
|
||||
|
||||
The relation is implemented in the [Product](../../references/entities/classes/Product.md) entity. You can access the sales channels a product is available in by expanding the `sales_channels` relation and using `product.sales_channels`.
|
||||
|
||||
### Cart
|
||||
|
||||
When you create a cart, you can optionally specify the sales channel it belongs to. If you don’t set the sales channel, the store’s default sales channel will be associated with it.
|
||||
|
||||
A cart can belong to only one sales channel. Only products that belong to the same sales channel as the cart can be added to it.
|
||||
|
||||
The relation is implemented in the [Cart](../../references/entities/classes/Cart.md) entity. You can access the sales channel a cart is associated with by expanding the `sales_channel` relation and using `cart.sales_channel`. You can also access the ID of the sales channel using `cart.sales_channel_id`.
|
||||
|
||||
### Order
|
||||
|
||||
Orders can optionally be associated with a sales channel. You can filter orders by a sales channel using the Storefront and Admin APIs.
|
||||
|
||||
The relation is implemented in the [Order](../../references/entities/classes/Order.md) entity. You can access the sales channel an order is associated with by expanding the `sales_channel` relation and using `order.sales_channel`. You can also access the ID of the sales channel using `order.sales_channel_id`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Manage sales channels using the admin APIs](./admin/manage.mdx)
|
||||
- [Use sales channels in the storefront](./storefront/use-sales-channels.mdx)
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: 'Learn how to implement sales channel functionalities in the storefront using REST APIs. This includes filtering products by Sales Channel and associating a cart with a sales channel.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Use Sales Channels in Storefronts
|
||||
|
||||
In this document, you’ll learn how to use sales channels in your storefront.
|
||||
|
||||
## Overview
|
||||
|
||||
In your storefront, you can filter products by sales channels. You can also associate carts with a sales channel.
|
||||
|
||||
This guide explains how to perform these operations using the Storefront APIs.
|
||||
|
||||
### Alternative Approach: Publishable API keys
|
||||
|
||||
This guide covers how to pass the sales channel ID into different requests to retrieve or process data associated with the sales channel.
|
||||
|
||||
An alternative approach is to use Publishable API keys. You can learn how to use them in the following documents:
|
||||
|
||||
- [Using publishable API keys in requests](../../../development/publishable-api-keys/index.mdx)
|
||||
- Managing publishable API keys using [admin APIs](../../../development/publishable-api-keys/admin/manage-publishable-api-keys.mdx) or using [Medusa Admin](../../../user-guide/settings/publishable-api-keys.mdx)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's 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.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### 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 installed](../../../js-client/overview.md) 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
---
|
||||
|
||||
## Filter Products by Sales Channel
|
||||
|
||||
To filter products by a specific sales channel, pass the `sales_channel_id` query parameter to the List Products endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
sales_channel_id: [
|
||||
salesChannelId,
|
||||
],
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { useProducts } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
sales_channel_id: [
|
||||
salesChannelId,
|
||||
],
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?sales_channel_id[0]=${salesChannelId}`)
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The `sales_channel_id` query parameter is an array of sales channel IDs. You can pass more than one sales channel.
|
||||
|
||||
The request returns an array of products. These are the products that are available in the sales channel.
|
||||
|
||||
---
|
||||
|
||||
## Associate a Cart with a Sales Channel
|
||||
|
||||
### When Creating a Cart
|
||||
|
||||
To associate a sales channel with a cart while creating it, you can pass the `sales_channel_id` request body parameter with the ID of the sales channel:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
|
||||
```ts
|
||||
medusa.carts.create({
|
||||
sales_channel_id: salesChannelId,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{
|
||||
sales_channel_id: salesChannelId,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sales_channel_id: salesChannelId,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request returns the created cart.
|
||||
|
||||
### Updating an Existing Cart
|
||||
|
||||
You can update the sales channel of an existing cart by passing the `sales_channel_id` request body parameter with the ID of the sales channel:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
sales_channel_id: salesChannelId,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeSalesChannel = (salesChannelId: string) => {
|
||||
updateCart.mutate({
|
||||
sales_channel_id: salesChannelId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sales_channel_id: salesChannelId,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => console.log(cart.id))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request returns the updated cart.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,306 @@
|
||||
---
|
||||
description: 'Learn how to import products into Medusa using the Admin REST APIs. The steps include uploading a CSV file, creating a batch job for the import, and confirming the batch job.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Tax Settings
|
||||
|
||||
In this document, you’ll learn how to manage tax settings using admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Tax settings are defined per region. You can change the tax settings of a region using the [Update Region endpoint](https://docs.medusajs.com/api/admin#regions_postregionsregion).
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List available tax providers in a store.
|
||||
- Change the tax provider of a region.
|
||||
- Change tax settings of a region.
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Tax Providers
|
||||
|
||||
You can list all tax providers of a store using the [List Tax Providers endpoint](https://docs.medusajs.com/api/admin#store_getstoretaxproviders):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.store.listTaxProviders()
|
||||
.then(({ tax_providers }) => {
|
||||
console.log(tax_providers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminStoreTaxProviders } from "medusa-react"
|
||||
|
||||
const TaxProviders = () => {
|
||||
const {
|
||||
tax_providers,
|
||||
isLoading,
|
||||
} = useAdminStoreTaxProviders()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{tax_providers && !tax_providers.length && (
|
||||
<span>No Tax Providers</span>
|
||||
)}
|
||||
{tax_providers && tax_providers.length > 0 && (
|
||||
<ul>
|
||||
{tax_providers.map((tax_provider) => (
|
||||
<li key={tax_provider.id}>{tax_provider.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaxProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/store/tax-providers`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ tax_providers }) => {
|
||||
console.log(tax_providers.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/store/tax-providers' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not accept any parameters.
|
||||
|
||||
The request returns an array of tax provider objects.
|
||||
|
||||
---
|
||||
|
||||
## Change Tax Provider of a Region
|
||||
|
||||
You can change the tax provider of a region using the [Update Region endpoint](https://docs.medusajs.com/api/admin#regions_postregionsregion):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.update(regionId, {
|
||||
tax_provider_id,
|
||||
})
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateRegion } from "medusa-react"
|
||||
|
||||
const UpdateRegion = () => {
|
||||
const updateRegion = useAdminUpdateRegion(regionId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateRegion.mutate({
|
||||
tax_provider_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateRegion
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions/${regionId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tax_provider_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/regions/<REGION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"tax_provider_id": "<TAX_PROVIDER_ID>"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the region to be passed as a path parameter.
|
||||
|
||||
In the body of the request, you can pass any of the region’s attributes to update. To update the tax provider of a region, you can pass the `tax_provider_id` request body parameter.
|
||||
|
||||
The request returns the updated region as an object.
|
||||
|
||||
---
|
||||
|
||||
## Change Tax Settings of a Region
|
||||
|
||||
In addition to changing the tax provider, you can use the same [Update Region endpoint](https://docs.medusajs.com/api/admin#regions_postregionsregion) to update the region’s other tax settings:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.regions.update(regionId, {
|
||||
tax_provider_id,
|
||||
automatic_taxes,
|
||||
gift_cards_taxable,
|
||||
tax_code,
|
||||
tax_rate,
|
||||
})
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateRegion } from "medusa-react"
|
||||
|
||||
const UpdateRegion = () => {
|
||||
const updateRegion = useAdminUpdateRegion(regionId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateRegion.mutate({
|
||||
tax_provider_id,
|
||||
automatic_taxes,
|
||||
gift_cards_taxable,
|
||||
tax_code,
|
||||
tax_rate,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateRegion
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/regions/${regionId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tax_provider_id,
|
||||
automatic_taxes,
|
||||
gift_cards_taxable,
|
||||
tax_code,
|
||||
tax_rate,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ region }) => {
|
||||
console.log(region.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/regions/<REGION_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"tax_provider_id": "<TAX_PROVIDER_ID>",
|
||||
"automatic_taxes": true,
|
||||
"gift_cards_taxable": true,
|
||||
"tax_code": "<TAX_CODE>",
|
||||
"tax_rate": 10
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can pass the following parameters in the body of the request that are related to tax settings:
|
||||
|
||||
- `automatic_taxes`: a boolean value indicating whether taxes should be calculated automatically when calculating totals. The default value is `true`.
|
||||
- `gift_cards_taxable`: a boolean value indicating whether gift cards are taxable. The default value is `true`.
|
||||
- `tax_code`: a string indicating the tax code of the region.
|
||||
- `tax_rate`: a number indicating the default tax rate of the region.
|
||||
|
||||
The request returns the updated region as an object.
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
description: 'Learn how to create a tax provider. You can create a tax provider in a Medusa backend or a plugin.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Tax Provider
|
||||
|
||||
In this document, you’ll learn how to create a tax provider.
|
||||
|
||||
## Overview
|
||||
|
||||
A tax provider is used to retrieve the tax lines in a cart. The Medusa backend provides a default `system` provider. You can create your own tax provider, either in a plugin or directly in your Medusa backend, then use it in any region.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Tax Provider Class
|
||||
|
||||
A tax provider class should be defined in a TypeScript or JavaScript file under `src/services` and the class should extend `AbstractTaxService` imported from `@medusajs/medusa`.
|
||||
|
||||
For example, you can create the file `src/services/my-tax.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-tax.ts
|
||||
import {
|
||||
AbstractTaxService,
|
||||
ItemTaxCalculationLine,
|
||||
ShippingTaxCalculationLine,
|
||||
TaxCalculationContext,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProviderTaxLine,
|
||||
} from "@medusajs/medusa/dist/types/tax-service"
|
||||
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
async getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
shippingLines: ShippingTaxCalculationLine[],
|
||||
context: TaxCalculationContext):
|
||||
Promise<ProviderTaxLine[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyTaxService
|
||||
```
|
||||
|
||||
Since the class extends `AbstractTaxService`, it must implement its abstract method `getTaxLines`, which is explained later in this guide.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
```ts title=src/services/my-tax.ts
|
||||
// ...
|
||||
import { LineItemService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
lineItemService: LineItemService
|
||||
}
|
||||
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
protected readonly lineItemService_: LineItemService
|
||||
|
||||
constructor({ lineItemService }: InjectedDependencies) {
|
||||
super()
|
||||
this.lineItemService_ = lineItemService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default MyTaxService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Define Identifier
|
||||
|
||||
Every tax provider must have a unique identifier. The identifier is defined as a static property in the class, and its value is used when registering the tax provider in the database and in the dependency container.
|
||||
|
||||
Add the static property `identifier` in your tax provider class:
|
||||
|
||||
```ts title=src/services/my-tax.ts
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
static identifier = "my-tax"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to change `my-tax` to the name of your tax provider.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Implement getTaxLines Method
|
||||
|
||||
The `getTaxLines` method is the only required method in a tax provider. It’s used when retrieving the tax lines for line items and shipping methods, typically during checkout or when calculating totals, for example, for orders, swaps, or returns.
|
||||
|
||||
The method accepts three parameters. The first parameter is an array of tax calculation objects for line items. Each object having the following properties:
|
||||
|
||||
- `item`: a line item object.
|
||||
- `rates`: an array of objects, each object having the following properties:
|
||||
- `rate`: an optional number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
|
||||
The second parameter is an array of tax calculation objects for shipping methods. Each object having the following properties:
|
||||
|
||||
- `shipping_method`: a shipping method object.
|
||||
- `rates`: an array of objects, each object having the following properties:
|
||||
- `rate`: an optional number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
|
||||
The third parameter is a context object that can be helpful for the tax calculation. The object can have the following properties:
|
||||
|
||||
- `shipping_address`: an optional address object used for shipping.
|
||||
- `customer`: an optional customer object.
|
||||
- `region`: an optional region object.
|
||||
- `is_return`: a boolean value that determines whether the taxes are being calculated for a return flow.
|
||||
- `shipping_methods`: an array of shipping methods being used in the current context.
|
||||
- `allocation_map`: an object that indicates the gift cards and discounts applied on line items. Each object key or property is an ID of a line item, and the value is an object having the following properties:
|
||||
- `gift_card`: an optional object indicating the gift card applied on the line item.
|
||||
- `discount`: an optional object indicating the discount applied on the line item.
|
||||
|
||||
This method is expected to return an array of line item tax line or shipping method tax line objects.
|
||||
|
||||
The line item tax line object has the following properties:
|
||||
|
||||
- `rate`: a number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
- `item_id`: the ID of the line item.
|
||||
- `metadata`: an optional object that can hold any necessary additional data to be added to the line item tax lines.
|
||||
|
||||
The shipping method tax line object has the following properties:
|
||||
|
||||
- `rate`: a number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
- `shipping_method_id`: the ID of the shipping method.
|
||||
- `metadata`: an optional object that can hold any necessary additional data to be added to the shipping method tax lines.
|
||||
|
||||
The returned array would be a combination of both the line item tax lines and shipping method tax lines.
|
||||
|
||||
:::note
|
||||
|
||||
The Medusa backend determines whether an object in the returned array is a shipping method tax line item, depending on the availability of the `shipping_method_id` attribute. For line items, it depends on the availability of the `item_id` attribute.
|
||||
|
||||
:::
|
||||
|
||||
For example, the `system` tax provider returns the tax calculation line items in the first parameter and the tax calculation shipping methods in the second parameter as is:
|
||||
|
||||
```ts title=src/services/my-tax.ts
|
||||
// ...
|
||||
|
||||
class SystemTaxService extends AbstractTaxService {
|
||||
// ...
|
||||
|
||||
async getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
shippingLines: ShippingTaxCalculationLine[],
|
||||
context: TaxCalculationContext
|
||||
): Promise<ProviderTaxLine[]> {
|
||||
let taxLines: ProviderTaxLine[] = itemLines.flatMap((l) => {
|
||||
return l.rates.map((r) => ({
|
||||
rate: r.rate || 0,
|
||||
name: r.name,
|
||||
code: r.code,
|
||||
item_id: l.item.id,
|
||||
}))
|
||||
})
|
||||
|
||||
taxLines = taxLines.concat(
|
||||
shippingLines.flatMap((l) => {
|
||||
return l.rates.map((r) => ({
|
||||
rate: r.rate || 0,
|
||||
name: r.name,
|
||||
code: r.code,
|
||||
shipping_method_id: l.shipping_method.id,
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
return taxLines
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run the Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Before you can test out your tax provider, you must enable it in a region. You can do that either using the [Medusa Admin dashboard](../../../user-guide/taxes/manage.md#change-tax-provider) or using the [Update Region admin endpoint](../admin/manage-tax-settings.mdx#change-tax-provider-of-a-region).
|
||||
|
||||
Then, you can test out the tax provider by simulating a checkout process in that region. You should see the line item tax lines in the cart’s `items`, as each item object has a `tax_lines` array which are the tax lines that you return in the `getTaxLines` method for line items.
|
||||
|
||||
Similarly, you should see the shipping method tax lines in the cart’s `shipping_methods`, as each shipping method object has a `tax_lines` array which are the tax lines that you return in the `getTaxLines` method for shipping methods.
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
description: 'Learn how to create a tax provider. You can create a tax provider in a Medusa backend or a plugin.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override a Tax Calculation Strategy
|
||||
|
||||
In this document, you’ll learn how to override a tax calculation strategy.
|
||||
|
||||
## Overview
|
||||
|
||||
A tax calculation strategy is used to calculate taxes when calculating totals. The Medusa backend provides a tax calculation strategy that handles calculating the taxes accounting for defined tax rates, and settings such as whether tax-inclusive pricing is enabled.
|
||||
|
||||
You can override the tax calculation strategy to implement different calculation logic or to integrate a third-party service that handles the tax calculation. You can override it either in a Medusa backend setup or in a plugin.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Strategy Class
|
||||
|
||||
A tax calculation strategy should be defined in a TypeScript or JavaScript file created under the `src/strategies` directory. The class must also implement the `ITaxCalculationStrategy` interface imported from the `@medusajs/medusa` package.
|
||||
|
||||
For example, you can create the file `src/strategies/tax-calculation.ts` with the following content:
|
||||
|
||||
```ts title=src/strategies/tax-calculation.ts
|
||||
import {
|
||||
ITaxCalculationStrategy,
|
||||
LineItem,
|
||||
LineItemTaxLine,
|
||||
ShippingMethodTaxLine,
|
||||
TaxCalculationContext,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class TaxCalculationStrategy
|
||||
implements ITaxCalculationStrategy {
|
||||
|
||||
async calculate(
|
||||
items: LineItem[],
|
||||
taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
calculationContext: TaxCalculationContext
|
||||
): Promise<number> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TaxCalculationStrategy
|
||||
```
|
||||
|
||||
Note that you add a basic implementation of the `calculate` method because it’s required by the `ITaxCalculationStrategy` interface. You’ll learn about the method later in the guide.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
```ts title=src/strategies/tax-calculation.ts
|
||||
// ...
|
||||
import {
|
||||
LineItemService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
lineItemService: LineItemService
|
||||
}
|
||||
|
||||
class TaxCalculationStrategy
|
||||
implements ITaxCalculationStrategy {
|
||||
|
||||
protected readonly lineItemService_: LineItemService
|
||||
|
||||
constructor({ lineItemService }: InjectedDependencies) {
|
||||
this.lineItemService_ = lineItemService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement the calculate Method
|
||||
|
||||
A tax calculation strategy is only required to implement the `calculate` method. This method is used whenever the totals are calculated.
|
||||
|
||||
:::tip
|
||||
|
||||
If automatic tax calculation is disabled, then the tax calculation strategy will only be used when taxes are calculated manually as explain [this guide](../storefront/manual-calculation.md).
|
||||
|
||||
:::
|
||||
|
||||
The `calculate` method expects three parameters:
|
||||
|
||||
- `items`: the first parameter is an array of [line item](../../../references/entities/classes/LineItem.md) objects.
|
||||
- `taxLines`: the second parameter is an array of either [shipping method tax line](../../../references/entities/classes/ShippingMethodTaxLine.md) or [line item tax line](../../../references/entities/classes/LineItemTaxLine.md) objects.
|
||||
- `calculationContext`: an object holding the context of the tax calculation. The object has the following properties:
|
||||
- `shipping_address`: an optional address object used for shipping.
|
||||
- `customer`: an optional customer object.
|
||||
- `region`: an optional region object.
|
||||
- `is_return`: a boolean value that determines whether the taxes are being calculated for a return flow.
|
||||
- `shipping_methods`: an array of shipping methods being used in the current context.
|
||||
- `allocation_map`: an object that indicates the gift cards and discounts applied on line items. Each object key or property is an ID of a line item, and the value is an object having the following properties:
|
||||
- `gift_card`: an optional object indicating the gift card applied on the line item.
|
||||
- `discount`: an optional object indicating the discount applied on the line item.
|
||||
|
||||
The method returns a number, being the tax amount for the line items, tax lines, and context provided.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
To test it out, you can simulate a checkout flow and check the calculated taxes to see if it matches the logic you implemented in the `calculate` method.
|
||||
|
||||
:::tip
|
||||
|
||||
As mentioned earlier, if automatic calculation of taxes is disabled in the region, you have to manually trigger tax calculation as explained in [this guide](../storefront/manual-calculation.md).
|
||||
|
||||
:::
|
||||
@@ -0,0 +1,206 @@
|
||||
---
|
||||
description: 'Learn what tax-inclusive pricing is and how it works in Medusa. Tax-inclusive pricing allows merchants to set the final prices for products and shipping options regardless of what tax rate is applicable for the customer.'
|
||||
---
|
||||
|
||||
# Tax Inclusive Pricing
|
||||
|
||||
In this document, you’ll learn how tax-inclusive pricing works in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
Tax Inclusive Pricing is currently in beta mode and guarded by a feature flag. To use Tax-Inclusive Pricing either:
|
||||
|
||||
1. Enable the `MEDUSA_FF_TAX_INCLUSIVE_PRICING` environment variable;
|
||||
2. Or enable the `tax_inclusive_pricing` 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
|
||||
|
||||
Tax Inclusive pricing allows you to set the final prices for products and shipping options regardless of the customer's applicable tax rates. When tax-inclusive prices are used, Medusa automatically calculates the tax amount for a given price.
|
||||
|
||||
This can be useful when some countries have the same currency but have different tax rates. If you want your prices to be the same across these countries, you have to manage two price lists to account for the tax differences. Using tax-inclusive pricing you only have to specify the price once.
|
||||
|
||||
Then, Medusa handles calculating the tax amount using the tax rate and the tax-inclusive price. This is managed in the backend and relayed to accounting and analytics tools.
|
||||
|
||||
---
|
||||
|
||||
## How is Tax Inclusivity Defined
|
||||
|
||||
Tax inclusivity can be toggled for regions, currencies, price lists, and shipping options either during creation or while editing. This is represented by the boolean attribute `includes_tax` available in the entities `Region`, `Currency`, `PriceList`, and `ShippingOption`. By default, this attribute is set to `false`.
|
||||
|
||||
If you want to enable or disable this attribute for any of these entities, you can use the create or update endpoints related to these entities as shown in the [Admin API reference](https://docs.medusajs.com/api/admin/).
|
||||
|
||||
The value set for these entities can affect whether line items and shipping methods are tax inclusive or not.
|
||||
|
||||
### How is Tax Inclusivity Defined for Line Items
|
||||
|
||||
:::info
|
||||
|
||||
When a product is added to the cart, a line item is created to represent that product in the cart.
|
||||
|
||||
:::
|
||||
|
||||
The `LineItem` entity also has the `includes_tax` attribute. The value of this flag is set to `true` if:
|
||||
|
||||
- The region of the line item has the `includes_tax` attribute set to `true`;
|
||||
- Or the currency of the line item has the `includes_tax` attribute set to `true`;
|
||||
- Or a price list that includes the product variant associated with the line item has the `includes_tax` attribute set to `true`, and the tax-inclusive amount of one of the variant’s prices in the price list is less than the original price of the variant;
|
||||
- Or one of the variant’s prices in the price list uses a currency or region that has the `includes_tax` attribute set to `true`, and the tax-inclusive amount of the price is less than the original price of the variant.
|
||||
|
||||
### How is Tax Inclusivity Defined for Shipping Methods
|
||||
|
||||
:::info
|
||||
|
||||
When a shipping option is selected, a shipping method is created based on that shipping option. You can learn more about this in the [Shipping Architecture documentation](../carts-and-checkout/shipping.md).
|
||||
|
||||
:::
|
||||
|
||||
The `ShippingMethod` entity also has the `includes_tax` attribute. Its value is the same as the value of `includes_tax` of the shipping option the method is associated with.
|
||||
|
||||
---
|
||||
|
||||
## Tax Amount Calculation Formula
|
||||
|
||||
When a price is tax-inclusive, the tax amount is calculated using the following formula:
|
||||
|
||||
```jsx noReport
|
||||
const taxAmount = (taxRate * taxInclusivePrice) / (1 + taxRate)
|
||||
```
|
||||
|
||||
Where `taxRate` is the tax rate to be applied to the price, and `taxInclusivePrice` is the price entered by the store operator.
|
||||
|
||||
For example, if the tax rate is `0.25` and the price of a product is `100`, the resulting tax amount calculated by Medusa will be `0.25 * 100 / 1.25 = 20`.
|
||||
|
||||
---
|
||||
|
||||
## Retrieving Tax Amounts
|
||||
|
||||
This section covers at which point tax amounts are calculated for different entities, how they are calculated when the price is tax inclusive, and what fields can be returned in the endpoints relative to each of the entities.
|
||||
|
||||
:::note
|
||||
|
||||
If you have disabled the automatic calculation of taxes in a region, you must [manually calculate the taxes of the line items and the cart](./storefront/manual-calculation.md). Otherwise, taxes will not be calculated or retrieved during checkout for each entity mentioned here.
|
||||
|
||||
:::
|
||||
|
||||
### Products
|
||||
|
||||
Taxes are calculated for each product variant either when a [single product is retrieved](https://docs.medusajs.com/api/store#products_getproductsproduct) or a [list of products is retrieved](https://docs.medusajs.com/api/store#products_getproducts).
|
||||
|
||||
Among the pricing fields retrieved for each variant, the following fields are relevant to taxes:
|
||||
|
||||
- `original_price`: The original price of the variant.
|
||||
- `calculated_price`: The calculated price, which can be based on prices defined in a price list.
|
||||
- `original_tax`: The tax amount applied to the original price.
|
||||
- `calculated_tax`: The tax amount applied to the calculated price.
|
||||
- `original_price_incl_tax`: The price after applying the tax amount on the original price.
|
||||
- `calculated_price_incl_tax`: The price after applying the tax amount on the calculated price
|
||||
- `original_price_includes_tax`: a boolean value indicating whether the amount in `original_price` includes the tax amount by default or not.
|
||||
- `calculated_price_includes_tax`: a boolean value indicating whether the amount in `calculated_price` includes the tax amount by default or not.
|
||||
|
||||
If tax inclusivity is enabled for the current region or currency (based on whether the default price is specified for the region or currency, with region taking a higher precedence):
|
||||
|
||||
- `original_price` will include the tax amount by default.
|
||||
- `original_price_includes_tax` will be set to `true`.
|
||||
- `original_price_incl_tax` will have the same amount as `original_price`.
|
||||
- `original_tax` is automatically calculated by Medusa.
|
||||
|
||||
Also, for each of the product variant’s prices in a price list, if tax inclusivity is enabled (either if the price list itself has the `includes_tax` attribute set to `true`, or the variant’s price in the price list uses a currency or region that has the `includes_tax` attribute set to `true`), and the amount of the price is less than the original price of the variant:
|
||||
|
||||
- `calculated_tax` will include the tax amount by default.
|
||||
- `calculated_price_includes_tax` will be set to `true`.
|
||||
- `calculated_price_incl_tax` will have the same amount as `calculated_price`.
|
||||
|
||||
:::info
|
||||
|
||||
Price lists include a list of prices that can be used to override the original price of a product’s variants.
|
||||
|
||||
Each variant’s price in the price list is compared to the variant’s original price using the following condition:
|
||||
|
||||
```jsx noReport
|
||||
amount < (1 + taxRate) * calculatedPrice
|
||||
```
|
||||
|
||||
Where `amount` is the amount of the variant’s price in the price list, `taxRate` is the applied rate, and `calculatedPrice` is the original price of the variant.
|
||||
|
||||
:::
|
||||
|
||||
Here is an example of these fields when tax inclusivity is enabled for both the currency and the price list:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js noReport
|
||||
{
|
||||
original_price: 110,
|
||||
calculated_price: 100,
|
||||
calculated_price_type: "sale",
|
||||
original_price_includes_tax: true,
|
||||
calculated_price_includes_tax: true,
|
||||
calculated_price_incl_tax: 100,
|
||||
calculated_tax: 20,
|
||||
original_price_incl_tax: 110,
|
||||
original_tax: 22,
|
||||
}
|
||||
```
|
||||
|
||||
### Line Item
|
||||
|
||||
The taxes of line items are calculated and retrieved whenever the [cart is retrieved or updated](https://docs.medusajs.com/api/store#carts).
|
||||
|
||||
Each line item returned in any of the cart’s requests has total fields related to the price of the line item and its taxes. Among those fields, the following are relevant to tax-inclusive pricing:
|
||||
|
||||
- `unit_price`: The original price of the variant associated with the line item.
|
||||
- `tax_total`: The total tax amount applied on the original price taking into account any applied discounts as well.
|
||||
- `original_tax_total`: The total tax amount applied on the original price without taking into account any applied discounts.
|
||||
- `subtotal`: The total of the line item’s price subtracting the amount in `original_tax_total`.
|
||||
- `origial_total`: The `subtotal` including the `original_tax_total` amount.
|
||||
|
||||
If tax inclusivity is enabled for the line item, `unit_price` will include the tax amount. The tax amount, which will also be the value of `tax_total`, is calculated using [Medusa’s formula for tax inclusive pricing](#tax-amount-calculation-formula) based on the line item’s tax rates. The calculation takes into account any discounts applied on the item, which means the discount amount is deducted from the original price.
|
||||
|
||||
Then, the `subtotal` is calculated by subtracting the `tax_total` from the total of the line item’s price. `original_total` has the same value as `subtotal`.
|
||||
|
||||
:::info
|
||||
|
||||
The total of the line item’s price is the variant’s `unit_price` multiplied by its quantity in the cart.
|
||||
|
||||
:::
|
||||
|
||||
Finally, `original_tax_total` undergoes the same `tax_total` calculation, however, any discounts applied on the line item are not taken into account. This means the discount amount is not deducted from the original price.
|
||||
|
||||
### Shipping Options
|
||||
|
||||
Taxes for Shipping Options are calculated and retrieved when the [list of shipping options is retrieved](https://docs.medusajs.com/api/store#shipping-options).
|
||||
|
||||
Among the returned fields for each shipping option, the following are relevant to each of their pricing and taxes:
|
||||
|
||||
- `amount`: The original price of the shipping option.
|
||||
- `price_incl_tax`: The price of the shipping option with tax included.
|
||||
- `tax_amount`: The tax amount applied to the shipping option.
|
||||
|
||||
If tax inclusivity is enabled for the shipping option, `amount` and `price_incl_tax` have the same value. Also, the value of `tax_amount` is calculated using [Medusa’s formula for tax inclusive pricing](#tax-amount-calculation-formula).
|
||||
|
||||
### Carts and Orders
|
||||
|
||||
Carts and Orders have the same total fields relevant for taxes.
|
||||
|
||||
A cart’s totals, including its taxes, are calculated and retrieved whenever the cart is [updated or retrieved](https://docs.medusajs.com/api/store#carts).
|
||||
|
||||
An order’s totals, including its taxes, are calculated and retrieved whenever the order is retrieved both on the [storefront](https://docs.medusajs.com/api/store#orders) and on the [admin](https://docs.medusajs.com/api/admin#orders).
|
||||
|
||||
The relevant fields are:
|
||||
|
||||
- `shipping_total`: The total tax-exclusive price of the shipping methods used in the cart or order without any applied taxes.
|
||||
- `tax_total`: The total of the taxes applied on the cart or order, including taxes applied on line items and shipping methods.
|
||||
- `subtotal`: The subtotal of line items including taxes, but without shipping total.
|
||||
- `total`: The total of the cart or order, including the subtotal, shipping total, and taxes applied.
|
||||
|
||||
During the calculation of the totals of different components of the cart or order, such as shipping or line items, if tax inclusivity is enabled on that component, a process similar to those explained above will be applied to retrieve the total.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Calculate taxes manually](./storefront/manual-calculation.md)
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
description: "Taxes are additional costs or fees imposed by a government for goods or services. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Taxes
|
||||
|
||||
Taxes are additional costs or fees imposed by a government for goods or services. This overview introduces the available features related to taxes.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Taxes user guide](../../user-guide/taxes/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Tax Providers
|
||||
|
||||
Developers can integrate third-party tax providers or implement custom tax-provider logic.
|
||||
|
||||
Admins can specify the tax provider for a specific region.
|
||||
|
||||
Customers can view calculated taxes during their checkout process.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/backend/create-tax-provider',
|
||||
label: 'Backend: Create Tax Provider',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a tax provider.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/backend/tax-calculation-strategy',
|
||||
label: 'Backend: Tax Calculation Strategy',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to override the tax calculation strategy.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/admin/manage-tax-settings',
|
||||
label: 'Admin: Manage Tax Settings',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage tax settings using Admin APIs.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/taxes/manage',
|
||||
label: 'User Guide: Manage Taxes',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage taxes in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/storefront/manual-calculation',
|
||||
label: 'Storefront: Manually Calculate Taxes',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manually calculate taxes during checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Tax Rates and Overrides
|
||||
|
||||
Admins can create additional tax rates aside from the default tax rate. They can also specify overrides of tax rates for specific products, product types, or shipping options.
|
||||
|
||||
Customers can review order edit requests and authorize additional payments if necessary.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/admin/manage-tax-rates',
|
||||
label: 'Admin: Manage Tax Rates',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage tax rates using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/taxes/tax-rates',
|
||||
label: 'User Guide: Tax Rates',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage tax rates using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/taxes/tax-overrides',
|
||||
label: 'User Guide: Tax Overrides',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage tax rates using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Tax-Inclusive Pricing
|
||||
|
||||
Admins can enable tax-inclusive pricing for a region or currency. This would automatically calculate the taxes applied on a product for that region or currency.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/inclusive-pricing',
|
||||
label: 'Architecture: Tax-Inclusive Pricing',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn how tax-inclusive pricing works and its relation to other modules.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/taxes/tax-inclusive',
|
||||
label: 'User Guide: Tax-Inclusive Pricing',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage tax-inclusive pricing using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how tax-related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes',
|
||||
label: 'Architecture: Tax',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Tax Architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/inclusive-pricing',
|
||||
label: 'Architecture: Tax-Inclusive Pricing',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn how tax-inclusive pricing works.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Taxes' relation to other modules in Medusa.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/regions-and-currencies/overview',
|
||||
label: 'Regions',
|
||||
customProps: {
|
||||
icon: Icons['globe-europe-solid'],
|
||||
description: 'Taxes, tax rates, and tax overrides are associated with a region.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/overview',
|
||||
label: 'Carts and Checkout',
|
||||
customProps: {
|
||||
icon: Icons['shopping-cart-solid'],
|
||||
description: 'Taxes can be automatically or manually calculated during checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
description: 'Learn how to manually calculate taxes during checkout in the Medusa backend. There are different methods including using endpoints or services.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Calculate Taxes Manually in Checkout
|
||||
|
||||
In this document, you’ll learn how to manually calculate taxes during checkout if you have automatic tax calculation disabled in a region.
|
||||
|
||||
## Overview
|
||||
|
||||
By default, taxes are automatically calculated by Medusa during checkout. This behavior can be disabled for a region using the Admin APIs or the Medusa admin to limit the requests being sent to a tax provider.
|
||||
|
||||
If you disable this behavior, you must manually trigger taxes calculation. When taxes are calculated, this means that requests will be sent to the tax provider to retrieve the tax rates.
|
||||
|
||||
---
|
||||
|
||||
## How to Manually Calculate Taxes in Checkout
|
||||
|
||||
This section explores different ways you can calculate taxes based on your purpose.
|
||||
|
||||
### Use Calculate Cart Taxes Endpoint
|
||||
|
||||
The [Calculate Cart Taxes](https://docs.medusajs.com/api/store#carts_postcartscarttaxes) endpoint forces the calculation of taxes for a cart during checkout. This bypasses the option set in admin to not calculate taxes automatically, which results in sending requests to the tax provider.
|
||||
|
||||
This calculates and retrieves the taxes on the cart and each of the line items in that cart.
|
||||
|
||||
### Use CartService's retrieve Method
|
||||
|
||||
The `CartService` class has a method `retrieve` that gets a cart by ID. In that method, taxes are calculated only if automatic taxes calculation is enabled for the region the cart belongs to.
|
||||
|
||||
You can, however, force calculating the taxes of the cart by passing in the third parameter an option containing the key `force_taxes` with its value set to `true`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
cartService.retrieve(
|
||||
"cart_01G8Z...",
|
||||
{ },
|
||||
{
|
||||
force_taxes: true,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn how to [retrieve and use services](../../../development/services/create-service.mdx#using-your-custom-service) in this documentation.
|
||||
|
||||
:::
|
||||
|
||||
### Use CartService's decorateTotals Method
|
||||
|
||||
Another way you can use the `CartService` to calculate taxes is using the method `decorateTotals`:
|
||||
|
||||
```jsx
|
||||
|
||||
export default () => {
|
||||
// ...
|
||||
|
||||
router.get("/store/line-taxes", async (req, res) => {
|
||||
// example of retrieving cart
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const cart = await cartService.retrieve(cart_id)
|
||||
|
||||
// ...
|
||||
// retrieve taxes of line items
|
||||
const data = await decorateTotals(cart, {
|
||||
force_taxes: true,
|
||||
})
|
||||
|
||||
return res.status(200).json({ cart: data })
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The `decorateTotals` method accepts the cart as a first parameter and an options object as a second parameter. If you set `force_taxes` to `true` in that object, the totals of the line items can be retrieved regardless of whether the automatic calculation is enabled in the line item’s region.
|
||||
|
||||
### Use TotalsService
|
||||
|
||||
You can calculate and retrieve taxes of line items using the `getLineItemTotals` method available in the `TotalService` class. All you need to do is pass in the third argument to that method an options object with the key `include_tax` set to true:
|
||||
|
||||
```jsx
|
||||
const itemTotals = await totalsService
|
||||
.getLineItemTotals(item, cart, {
|
||||
include_tax: true,
|
||||
})
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn how to [retrieve and use services](../../../development/services/create-service.mdx#using-your-custom-service) in this documentation.
|
||||
|
||||
:::
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
description: 'Learn about taxes in Medusa. This includes learning about the usage of taxes in regions, tax rates, tax providers, and more.'
|
||||
---
|
||||
|
||||
# Taxes Architecture Overview
|
||||
|
||||
In this document, you’ll learn about taxes in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Taxes are fees applied on items and shipping options when making a purchase. Taxes are typically different for each region around the world, both in amount and calculation logic.
|
||||
|
||||
In Medusa, there are different ways you can customize taxes, allowing you to implement what works for your setup or the regions your commerce store operates in. You can:
|
||||
|
||||
- Create a tax provider that defines the tax lines applied to line items and shipping methods.
|
||||
- Customize the tax calculation strategy to change how taxes are calculated.
|
||||
- Override tax rates for specific products, product types, or shipping options in a region using endpoints or services.
|
||||
|
||||
---
|
||||
|
||||
## Taxes in Regions
|
||||
|
||||
Since taxes are different for each country or region, taxes are configured per region.
|
||||
|
||||
In the `Region` entity, the following attributes are related to taxes:
|
||||
|
||||
- `tax_rate`: a number indicating the default tax rate to be applied in the region.
|
||||
- `tax_code`: a string indicating the tax code of the region. This can be useful to external systems for accounting purposes.
|
||||
- `gift_card_taxable`: a boolean value indicating whether gift cards in this region are taxable or not. By default, it’s `true`.
|
||||
- `automatic_taxes`: a boolean value indicating if taxes should be calculated automatically during checkout in that region. By default, it’s `true`.
|
||||
- `tax_provider_id`: the ID of the tax provider used in the region. By default, the `system` tax provider is used. The tax provider can also be accessed by expanding the `tax_provider` relation and accessing `region.tax_provider`.
|
||||
- `includes_tax`: a boolean value indicating if tax-inclusive pricing is enabled in the region. You can learn about tax-inclusive pricing [here](./inclusive-pricing.md).
|
||||
|
||||
### Tax Rates
|
||||
|
||||
A region has a default tax rate, defined in the `tax_rate` attribute. However, you can override this tax rate for specific products, product types, or shipping options in that region.
|
||||
|
||||
This is represented by the `TaxRate` entity. A region can have many tax rates, and the taxes rates can be accessed by expanding the `tax_rates` relation and accessing `region.tax_rates`.
|
||||
|
||||
Since a tax rate can be used to override taxes for specific conditions, the `TaxRate` entity has the following relations:
|
||||
|
||||
- `products`: an array of products that this tax rate applies to. You can access them by expanding the `products` relation and accessing `tax_rate.products`.
|
||||
- `product_types`: an array of product types that this tax rate applies to. You can access them by expanding the `product_types` relation and accessing `tax_rate.product_types`.
|
||||
- `shipping_options`: an array of shipping options that this tax rate applies to. You can access them by expanding the `shipping_options` relation and accessing `tax_rate.shipping_options`.
|
||||
|
||||
The `TaxRate` entity’s attributes include:
|
||||
|
||||
- `rate`: a number indicating the tax rate to apply for the specified conditions.
|
||||
- `code`: a string indicating the tax code of this rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `region_id`: a string indicating the ID of the region this tax rate applies to. You can also access the region by expanding the `region` relation and accessing `tax_rate.region`.
|
||||
|
||||
### Tax Provider
|
||||
|
||||
Tax providers are used to return the tax line items for a set of line items and shipping methods during checkout. The Medusa backend has a `system` tax provider. It returns the line items as-is, without making any changes. You can also create your own tax provider that matches your use case.
|
||||
|
||||
Each region can use a different tax provider. By default, regions use the `system` tax provider.
|
||||
|
||||
For example, the tax provider allows you to use the shipping address of the cart to create tax lines.
|
||||
|
||||
---
|
||||
|
||||
## Tax Lines
|
||||
|
||||
During checkout, taxes are applied to line items and shipping methods as tax lines. The taxes applied to line items are represented by the `LineItemTaxLine` entity, and those applied to shipping methods are represented by the `ShippingMethodTaxLine` entity. Both entities extend the `TaxLine` class, which defines the following attributes:
|
||||
|
||||
- `rate`: a number indicating the applied tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: a string indicating the tax code.
|
||||
|
||||
In addition to these attributes, the `LineItemTaxLine` entity has an `item_id` attribute, which is a string indicating the ID of the line item this tax line is applied to. You can also access the line item by expanding the `item` relation and accessing `line_item_tax_line.item`.
|
||||
|
||||
On the other hand, the `ShippingMethodTaxLine` has the attributes of `TaxLine` along with the `shipping_method_id` attribute, which is a string indicating the ID of the shipping method this tax line is applied to. You can also access the shipping method by expanding the `shipping_method` relation and accessing `shipping_method_tax_line.shipping_method`.
|
||||
|
||||
---
|
||||
|
||||
## Tax Calculation Strategy
|
||||
|
||||
The tax calculation strategy is used to calculate taxes based on a set of line items, tax lines, and a context during checkout. The strategy is used throughout the Medusa backend, such as when calculating the totals of a cart.
|
||||
|
||||
Medusa defines a default tax calculation strategy. It calculates the taxes of line items and shipping methods, and returns the total of the two.
|
||||
|
||||
You can override the tax calculation strategy to define a calculation strategy that works for your use case.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Tax Inclusive Pricing](./inclusive-pricing.md)
|
||||
- [How to calculate taxes manually during checkout](./storefront/manual-calculation.md)
|
||||
@@ -0,0 +1,455 @@
|
||||
---
|
||||
description: 'Learn how to manage invites using the admin API. This includes listing, creating, accepting, resending, and deleting invites.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Invites
|
||||
|
||||
In this document, you’ll learn how to manage invites using the admin API.
|
||||
|
||||
## Overview
|
||||
|
||||
You can use the invites admin API to manage and perform functionalities related to invites.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List invites
|
||||
- Create an invite
|
||||
- Accept an invite
|
||||
- Resend an invite
|
||||
- Delete an invite
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Authenticated Admin User
|
||||
|
||||
Except for the Accept Invite endpoint, 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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Invites
|
||||
|
||||
You can list invites by sending a request to the [List Invite endpoint](https://docs.medusajs.com/api/admin#invites_getinvites):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.invites.list()
|
||||
.then(({ invites }) => {
|
||||
console.log(invites.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminInvites } from "medusa-react"
|
||||
|
||||
const Invites = () => {
|
||||
const { invites, isLoading } = useAdminInvites()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{invites && !invites.length && <span>No Invites</span>}
|
||||
{invites && invites.length > 0 && (
|
||||
<ul>
|
||||
{invites.map((invite) => (
|
||||
<li key={invite.id}>{invite.user_email}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Invites
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/invites`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ invites }) => {
|
||||
console.log(invites.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/invites' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not accept any parameters.
|
||||
|
||||
The request returns an array of invite endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Create Invite
|
||||
|
||||
You can create an invite by sending a request to the [Create Invite endpoint](https://docs.medusajs.com/api/admin#invites_postinvites):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.invites.create({
|
||||
user: "user@example.com",
|
||||
role: "admin",
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateInvite } from "medusa-react"
|
||||
|
||||
const CreateInvite = () => {
|
||||
const createInvite = useAdminCreateInvite()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createInvite.mutate({
|
||||
user: "user@example.com",
|
||||
role: "admin",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateInvite
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/invites`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: "user@example.com",
|
||||
role: "admin",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/invites' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"user": "user@example.com",
|
||||
"role": "admin"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following body parameters:
|
||||
|
||||
- `user`: a string indicating the email of the user.
|
||||
- `role`: a string indicating the role of the user. Its values can be `admin`, `member`, and `developer`.
|
||||
|
||||
The request does not return any data. If the invite was created successfully, the status code of the response will be `200`.
|
||||
|
||||
---
|
||||
|
||||
## Accept an Invite
|
||||
|
||||
A logged-out user can accept an invite, which would create a user for that user.
|
||||
|
||||
You can accept an invite by sending a request to the [Accept Invite endpoint](https://docs.medusajs.com/api/admin#invites_postinvitesinviteaccept):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.invites.accept({
|
||||
token,
|
||||
user: {
|
||||
first_name: "Brigitte",
|
||||
last_name: "Collier",
|
||||
password: "supersecret",
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminAcceptInvite } from "medusa-react"
|
||||
|
||||
const AcceptInvite = () => {
|
||||
const acceptInvite = useAdminAcceptInvite()
|
||||
// ...
|
||||
|
||||
const handleAccept = () => {
|
||||
acceptInvite.mutate({
|
||||
token,
|
||||
user: {
|
||||
first_name: "Brigitte",
|
||||
last_name: "Collier",
|
||||
password: "supersecret",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default AcceptInvite
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/invites/accept`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
user: {
|
||||
first_name: "Brigitte",
|
||||
last_name: "Collier",
|
||||
password: "supersecret",
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/invites/accept' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "<TOKEN>",
|
||||
"user": {
|
||||
"first_name": "Brigitte",
|
||||
"last_name": "Collier",
|
||||
"password": "supersecret"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `token`: a string indicating the invitation’s token.
|
||||
- `user`: an object that has the following properties:
|
||||
- `first_name`: a string indicating the first name of the user.
|
||||
- `last_name`: a string indicating the last name of the user.
|
||||
- `password`: a string indicating the user’s password.
|
||||
|
||||
The request does not return any data. If the invite was accepted successfully, the status code of the response will be `200`.
|
||||
|
||||
---
|
||||
|
||||
## Resend an Invite
|
||||
|
||||
You can resend an invite if it’s not accepted yet. To resend an invite, send a request to the [Resend Invite endpoint](https://docs.medusajs.com/api/admin#invites_postinvitesinviteresend):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.invites.resend(inviteId)
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminResendInvite } from "medusa-react"
|
||||
|
||||
const ResendInvite = () => {
|
||||
const resendInvite = useAdminResendInvite(inviteId)
|
||||
// ...
|
||||
|
||||
const handleResend = () => {
|
||||
resendInvite.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ResendInvite
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/invites/${inviteId}/resend`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/invites/<INVITE_ID>/resend' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the invite ID as a path parameter.
|
||||
|
||||
The request does not return any data. If the invite was resent successfully, the status code of the response will be `200`.
|
||||
|
||||
---
|
||||
|
||||
## Delete an Invite
|
||||
|
||||
You can delete an invite by sending a request to the [Delete Invite endpoint](https://docs.medusajs.com/api/admin#invites_deleteinvitesinvite):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.invites.delete(inviteId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteInvite } from "medusa-react"
|
||||
|
||||
const DeleteInvite = () => {
|
||||
const deleteInvite = useAdminDeleteInvite(inviteId)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteInvite.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteInvite
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/invites/${inviteId}`, {
|
||||
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/invites/<INVITE_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the invite ID as a path parameter.
|
||||
|
||||
It deletes the invite and returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted invite.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `invite`.
|
||||
- `deleted`: A boolean value indicating whether the invite was deleted.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to implement user profiles](./manage-profile.mdx)
|
||||
- [How to manage users](./manage-users.mdx)
|
||||
@@ -0,0 +1,528 @@
|
||||
---
|
||||
description: 'Learn how to implement user profile management features using the admin APIs. This includes user authentication, updating the profile, and resetting the password.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Implement User Profiles
|
||||
|
||||
In this document, you’ll learn how to implement user profile management features using the admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
The user’s admin APIs allow you to retrieve and perform admin functionalities on users.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- User authentication, meaning user log in and log out.
|
||||
- Manage profile, including retrieving profile details and updating profile.
|
||||
- Reset password
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
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, JavaScript’s Fetch API, or cURL.
|
||||
|
||||
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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Authenticated Admin User
|
||||
|
||||
Aside from the User Login and Reset Password steps, other endpoints require you to be an authenticated admin user.
|
||||
|
||||
You can learn more about [authenticating as an admin user in the API reference](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## User Authentication
|
||||
|
||||
### User Login
|
||||
|
||||
You can log in a user by sending a request to the [User Login endpoint](https://docs.medusajs.com/api/admin#auth_postauth):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.auth.createSession({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminLogin } from "medusa-react"
|
||||
|
||||
const Login = () => {
|
||||
const adminLogin = useAdminLogin()
|
||||
// ...
|
||||
|
||||
const handleLogin = () => {
|
||||
adminLogin.mutate({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Login
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/auth`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/auth' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `email`: a string indicating the user’s email.
|
||||
- `password`: a string indicating the user’s password.
|
||||
|
||||
The request returns the logged-in user as an object.
|
||||
|
||||
### User Logout
|
||||
|
||||
You can log out a user by sending a request to the [User Logout endpoint](https://docs.medusajs.com/api/admin#auth_deleteauth):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.auth.deleteSession()
|
||||
.then(() => {
|
||||
// logged out successfully
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteSession } from "medusa-react"
|
||||
|
||||
const Logout = () => {
|
||||
const adminLogout = useAdminDeleteSession()
|
||||
// ...
|
||||
|
||||
const handleLogout = () => {
|
||||
adminLogout.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Logout
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/auth`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
// logged out successfully
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/auth' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The endpoint does not require any path or query parameters.
|
||||
|
||||
The request does not return any data. The response code will be `200` for successful log out.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve User Profile Details
|
||||
|
||||
You can retrieve the current user’s details for their profile by sending a request to the [Get Current User endpoint](https://docs.medusajs.com/api/admin#auth_getauth):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.auth.getSession()
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminGetSession } from "medusa-react"
|
||||
|
||||
const Profile = () => {
|
||||
const { user, isLoading } = useAdminGetSession()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{user && <span>{user.email}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/auth`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/auth' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any parameters.
|
||||
|
||||
The request returns the current user as an object.
|
||||
|
||||
---
|
||||
|
||||
## Update User’s Profile Details
|
||||
|
||||
You can update a user’s details in their profile by sending a request to the [Update User endpoint](https://docs.medusajs.com/api/admin#users_postusersuser):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.update(userId, {
|
||||
first_name: "Marcellus",
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminDeleteSession,
|
||||
useAdminUpdateUser,
|
||||
} from "medusa-react"
|
||||
|
||||
const Profile = () => {
|
||||
const updateUser = useAdminUpdateUser(userId)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateUser.mutate({
|
||||
first_name: "Marcellus",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Profile
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users/${userId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: "Marcellus",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/users/<USER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"first_name": "Marcellus"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the user as a path parameter.
|
||||
|
||||
In the request body, you can pass any of the user’s fields that you want to update as a parameter. In the example above, you pass the `first_name` parameter to update the user’s first name. You can refer to the [API reference](https://docs.medusajs.com/api/admin#users_postusersuser) to learn about other available parameters.
|
||||
|
||||
The request returns the updated user as an object.
|
||||
|
||||
---
|
||||
|
||||
## Reset Password
|
||||
|
||||
This section explains how you can reset the password of a user if they forgot their password.
|
||||
|
||||
### Step 1: Request Password Reset
|
||||
|
||||
The first step is to request a password reset. This would create in the Medusa backend a reset password token, which you typically would use in an email sent to the user. The email would include a link that allows the user to enter a new password, and the link would accept a token query parameter to be used in step 2.
|
||||
|
||||
:::note
|
||||
|
||||
Sending the password reset email is not handled by default in the Medusa backend. You can either use the SendGrid plugin which handles it, or manually subscribe to the `user.password_reset` event and send the email.
|
||||
|
||||
:::
|
||||
|
||||
You can request a password reset by sending a request to the [Request Password Reset endpoint](https://docs.medusajs.com/api/admin#users_postusersuserpasswordtoken):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.sendResetPasswordToken({
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminSendResetPasswordToken } from "medusa-react"
|
||||
|
||||
const Login = () => {
|
||||
const requestPasswordReset = useAdminSendResetPasswordToken()
|
||||
// ...
|
||||
|
||||
const handleResetPassword = () => {
|
||||
requestPasswordReset.mutate({
|
||||
email: "user@example.com",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Login
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users/password-token`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
// successful
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/users/password-token' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the `email` parameter in the request body, which is the email of the user requesting to reset their password.
|
||||
|
||||
The request does not return any data. The response code will be `204` if the request was processed successfully.
|
||||
|
||||
### Step 2: Reset Password
|
||||
|
||||
After the user resets their password and, typically, receives an email with a link to reset their password, they should enter their new password. The new password, along with the token passed to this page are used to reset the password on the Medusa backend.
|
||||
|
||||
You can reset the password by sending a request to the [Reset Password endpoint](https://docs.medusajs.com/api/admin#users_postusersuserpassword):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.resetPassword({
|
||||
token: "supersecrettoken",
|
||||
password: "supersecret",
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminResetPassword } from "medusa-react"
|
||||
|
||||
const ResetPassword = () => {
|
||||
const resetPassword = useAdminResetPassword()
|
||||
// ...
|
||||
|
||||
const handleResetPassword = () => {
|
||||
resetPassword.mutate({
|
||||
token: "supersecrettoken",
|
||||
password: "supersecret",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ResetPassword
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users/reset-password`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: "supersecrettoken",
|
||||
password: "supersecret",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/users/reset-password' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "supersecrettoken",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `token`: a string indicating the password reset token.
|
||||
- `password`: a string indicating the new password for the user.
|
||||
|
||||
You can also optionally pass the `email` parameter in the request body.
|
||||
|
||||
The request returns the user as an object, and the user is automatically logged in.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage users](./manage-users.mdx)
|
||||
@@ -0,0 +1,365 @@
|
||||
---
|
||||
description: 'Learn how to manage users using the admin APIs. This includes listing, creating, updating, and deleting users.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Users
|
||||
|
||||
In this document, you’ll learn how to manage users using the admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
You can use the user admin API to manage users and teams in the store.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- List users
|
||||
- Create a user
|
||||
- Update a user
|
||||
- Delete a user
|
||||
|
||||
---
|
||||
|
||||
## 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.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#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](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Users
|
||||
|
||||
You can retrieve users in a store by sending a request to the [List Users endpoint](https://docs.medusajs.com/api/admin#users_getusers):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.list()
|
||||
.then(({ users }) => {
|
||||
console.log(users.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUsers } from "medusa-react"
|
||||
|
||||
const Users = () => {
|
||||
const { users, isLoading } = useAdminUsers()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{users && !users.length && <span>No Users</span>}
|
||||
{users && users.length > 0 && (
|
||||
<ul>
|
||||
{users.map((user) => (
|
||||
<li key={user.id}>{user.email}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Users
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ users }) => {
|
||||
console.log(users.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/users' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any parameters.
|
||||
|
||||
The request returns an array of user objects.
|
||||
|
||||
---
|
||||
|
||||
## Create a User
|
||||
|
||||
You can create a user by sending a request to the [Create User endpoint](https://docs.medusajs.com/api/admin#users_postusers):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.create({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateUser } from "medusa-react"
|
||||
|
||||
const CreateUser = () => {
|
||||
const createUser = useAdminCreateUser()
|
||||
// ...
|
||||
|
||||
const handleCreateUser = () => {
|
||||
createUser.mutate({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateUser
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
password: "supersecret",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/users' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the following request body parameters:
|
||||
|
||||
- `email`: a string indicating the email of the user.
|
||||
- `password`: a string indicating the password of the user.
|
||||
|
||||
The endpoint accepts other optional body parameters, such as first name or last name. Check the [API reference](https://docs.medusajs.com/api/admin#users_postusers) for details on optional body parameters.
|
||||
|
||||
The request returns the created user as an object.
|
||||
|
||||
---
|
||||
|
||||
## Update User
|
||||
|
||||
You can update a user’s details by sending a request to the [Update User endpoint](https://docs.medusajs.com/api/admin#users_postusersuser):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.update(userId, {
|
||||
first_name: "Marcellus",
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateUser } from "medusa-react"
|
||||
|
||||
const UpdateUser = () => {
|
||||
const updateUser = useAdminUpdateUser(userId)
|
||||
// ...
|
||||
|
||||
const handleUpdateUser = () => {
|
||||
updateUser.mutate({
|
||||
first_name: "Marcellus",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateUser
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users/${userId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: "Marcellus",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ user }) => {
|
||||
console.log(user.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/users/<USER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"first_name": "Marcellus"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the ID of the user as a path parameter.
|
||||
|
||||
In the request body, you can pass any of the user’s fields that you want to update as parameters. In the example above, you update the user’s `first_name`. Check the [API reference](https://docs.medusajs.com/api/admin#users_postusersuser) for all the optional parameters.
|
||||
|
||||
The request returns the updated user as an object.
|
||||
|
||||
---
|
||||
|
||||
## Delete a User
|
||||
|
||||
You can delete a user by sending a request to the [Delete User endpoint](https://docs.medusajs.com/api/admin#users_deleteusersuser):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.users.delete(userId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteUser } from "medusa-react"
|
||||
|
||||
const DeleteUser = () => {
|
||||
const deleteUser = useAdminDeleteUser(userId)
|
||||
// ...
|
||||
|
||||
const handleDeleteUser = () => {
|
||||
deleteUser.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteUser
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/users/${userId}`, {
|
||||
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/users/<USER_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the user ID as a path parameter.
|
||||
|
||||
It deletes the user and returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted user.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `user`.
|
||||
- `deleted`: A boolean value indicating whether the user was deleted.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage a user’s profile](./manage-profile.mdx)
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
description: "Learn how to send an invitation email to an invited user. This guide uses SendGrid as an example."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Send an Invitation Email
|
||||
|
||||
In this document, you’ll learn how to send an invitation email to an invited user.
|
||||
|
||||
## Overview
|
||||
|
||||
Users can be invited to join a store by other users. When a new invite is created, the `invite.created` event is triggered. This event is also triggered when an invite is resent.
|
||||
|
||||
This guide explains how to subscribe to that event and send an email to the new user with the invitation link. The guide uses SendGrid as an example of illustration, but you can use any other notification service.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Event Bus Module
|
||||
|
||||
The event bus module trigger the event to the listening subscribers. So, it’s required to have an event bus module installed and configured on your Medusa backend.
|
||||
|
||||
The [Local Event Bus module](../../../development/events/modules/local.md) works in a development environment. For production, it’s recommended to use the [Redis Event Bus module](../../../development/events/modules/redis.md).
|
||||
|
||||
### Notification Provider
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Subscriber
|
||||
|
||||
To subscribe to and handle an event, you must create a subscriber.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
|
||||
|
||||
:::
|
||||
|
||||
Create the file `src/subscribers/invite.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/invite.ts
|
||||
type InjectedDependencies = {
|
||||
// TODO add necessary dependencies
|
||||
}
|
||||
|
||||
class InviteSubscriber {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// TODO subscribe to event
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSubscriber
|
||||
```
|
||||
|
||||
You’ll be adding in the next step the necessary dependencies to the subscriber.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Subscribe to the Event
|
||||
|
||||
In this step, you’ll subscribe to the `invite.created` event to send the user the invitation email.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
### Method 1: Using the NotificationService
|
||||
|
||||
If the notification provider you’re using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
|
||||
|
||||
```ts title=src/subscribers/invite.ts
|
||||
import { NotificationService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
notificationService: NotificationService
|
||||
}
|
||||
|
||||
class InviteSubscriber {
|
||||
constructor({ notificationService }: InjectedDependencies) {
|
||||
notificationService.subscribe(
|
||||
"invite.created",
|
||||
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSubscriber
|
||||
```
|
||||
|
||||
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
|
||||
:::
|
||||
|
||||
### Method 2: Using the EventBusService
|
||||
|
||||
If the notification provider you’re using isn’t configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
|
||||
|
||||
```ts title=src/subscribers/invite.ts
|
||||
import { EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
class InviteSubscriber {
|
||||
constructor({ eventBusService }: InjectedDependencies) {
|
||||
eventBusService.subscribe(
|
||||
"invite.created",
|
||||
this.handleInvite
|
||||
)
|
||||
}
|
||||
|
||||
handleInvite = async (data: Record<string, any>) => {
|
||||
// TODO: handle event
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSubscriber
|
||||
```
|
||||
|
||||
When using this method, you’ll have to handle the logic of sending the invitation email inside the handler function, which in this case is `handleInvite`.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Handle the Event
|
||||
|
||||
The `handleInvite` method receives a `data` object as a parameter which is a payload emitted when the event was triggered. This object has the following properties:
|
||||
|
||||
- `id`: a string indicating the ID of the invite.
|
||||
- `token`: a string indicating the token of the invite. This token is useful to pass along to a frontend link that can be used to accept the invite.
|
||||
- `user_email`: a string indicating the email of the invited user.
|
||||
|
||||
In this method, you should typically send an email to the invited user. You can place any content in the email, but typically you would include a link to your frontend that allows the invited user to enter their details and accept the invite.
|
||||
|
||||
### Example: Using SendGrid
|
||||
|
||||
For example, you can implement this subscriber to send emails using SendGrid:
|
||||
|
||||
```ts title=src/subscribers/invite.ts
|
||||
import { EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
sendgridService: any
|
||||
}
|
||||
|
||||
class InviteSubscriber {
|
||||
protected sendGridService: any
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
}: InjectedDependencies) {
|
||||
this.sendGridService = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"invite.created",
|
||||
this.handleInvite
|
||||
)
|
||||
}
|
||||
|
||||
handleInvite = async (data: Record<string, any>) => {
|
||||
this.sendGridService.sendEmail({
|
||||
templateId: "send-invite",
|
||||
from: "hello@medusajs.com",
|
||||
to: data.user_email,
|
||||
data: {
|
||||
// any data necessary for your template...
|
||||
token: data.token,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSubscriber
|
||||
```
|
||||
|
||||
Notice that you should replace the values in the object passed to the `sendEmail` method:
|
||||
|
||||
- `templateId`: Should be the ID of your invitation email template in SendGrid.
|
||||
- `from`: Should be the from email.
|
||||
- `to`: Should be the invited user’s email.
|
||||
- `data`: Should be an object holding any data that should be passed to your SendGrid email template. In the example above, you pass the token, which you can use in the SendGrid template to format the frontend link (for example, `<FRONTEND_LINK>/invite?token={{token}}`, where `<FRONTEND_LINK>` is your frontend’s hostname.)
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: "Users are admins that can manage the ecommerce store’s data and operations. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Users
|
||||
|
||||
Users are admins that can manage the ecommerce store’s data and operations. This overview introduces the available features related to users.
|
||||
|
||||
:::note
|
||||
|
||||
Not a developer? Check out the [Users user guide](../../user-guide/users/index.md).
|
||||
|
||||
:::
|
||||
|
||||
## Features
|
||||
|
||||
### Team Management
|
||||
|
||||
There can be more than one admin in the team managing the ecommerce store. Admins can manage other users in that team.
|
||||
|
||||
Admins can also manage their profile details.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/users/admin/manage-users',
|
||||
label: 'Admin: Manage Users',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage users using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/users/team',
|
||||
label: 'User Guide: Manage Team',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage the team in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/users/admin/manage-profile',
|
||||
label: 'Admin: Implement Profiles',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement user profiles using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/users/profile',
|
||||
label: 'User Guide: Manage Profile',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage the user\'s profile in Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### User Invites
|
||||
|
||||
Admins can invite users to join their team. Invites can be resent, accepted, or removed.
|
||||
|
||||
- Admin: Manage Invites
|
||||
- User Guide: Manage Invites
|
||||
- Backend: Send Invite
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/users/admin/manage-invites',
|
||||
label: 'Admin: Manage Invites',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage invites using Admin APIs.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/users/team#manage-invites',
|
||||
label: 'User Guide: Manage Invites',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage invites using Medusa Admin.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/users/backend/send-invite',
|
||||
label: 'Backend: Send Invite',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to send invites in the backend.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how user-related entities and concepts are built, their relation to other modules, and more.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/modules/users',
|
||||
label: 'Architecture: User',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the User Architecture.',
|
||||
}
|
||||
}} />
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
description: "Users are admins that can manage the ecommerce store’s data and operations. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
# Users Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the users architecture and invites in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
A user is an admin that can view and process sensitive and private information in the commerce store. A store in Medusa can have more than one user. Users can create or invite other users to manage the store.
|
||||
|
||||
:::tip
|
||||
|
||||
A user is typically referred to as “admin” throughout the documentation and user guide.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## User Entity Overview
|
||||
|
||||
Some of the `User` entity attributes include:
|
||||
|
||||
- `email`: a unique string indicating the email of the user.
|
||||
- `password_hash`: a string indicating the encrypted password of the user. Passwords are encrypted using the [scrypt-kdf NPM package](https://www.npmjs.com/package/scrypt-kdf). The password hash is nullable, which can be useful if you want to integrate a third-party authentication service that does not require a password.
|
||||
- `first_name`: a string indicating the user’s first name.
|
||||
- `last_name`: a string indicating the user’s last name.
|
||||
- `api_token`: a string that holds the user’s API token. The API token can be used to send authenticated requests to admin endpoints, instead of using cookie session authentication. Check out the [API reference](https://docs.medusajs.com/api/admin#authentication) to learn how to use it.
|
||||
- `role`: a string that indicates the role of the user. Its value can be either `admin`, `member`, or `developer`.
|
||||
|
||||
:::note
|
||||
|
||||
The `role` attribute does not actually provide permission or Access Control List (ACL) features within Medusa.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Invites Overview
|
||||
|
||||
A user can create other users where they specify the user’s details and credentials, and the new user can immediately authenticate using their credentials.
|
||||
|
||||
Alternatively, a user can invite another user to join by just supplying the new user’s email. Then, the new user can accept the invite and provide their credentials.
|
||||
|
||||
### Invite Entity Overview
|
||||
|
||||
An invitation is represented by the `Invite` entity. Some of its attributes include:
|
||||
|
||||
- `user_email`: a string indicating the email of the user.
|
||||
- `role`: a string indicating the role of the user. Similar to the `User` entity’s `role` attribute, its value can be either `admin`, `member`, or `developer`.
|
||||
- `accepted`: a boolean value indicating whether the invite has been accepted.
|
||||
- `token`: a string that is automatically generated when the invite is created. It’s a hash that is used to later accept the invitation.
|
||||
- `expires_at`: a date indicating when the invitation expires.
|
||||
|
||||
### Invite Process Overview
|
||||
|
||||
You have full freedom in how you choose to implement the invite flow. This section explains how it’s implemented within the Medusa backend.
|
||||
|
||||

|
||||
|
||||
The invitation process typically follows these steps in the Medusa backend:
|
||||
|
||||
1. A user creates an invite either using the [Create Invite endpoint](https://docs.medusajs.com/api/admin#invites_postinvites) or the `InviteService`'s `create` method. Part of creating an invite includes generating the token and setting the expiry date. By default, the expiry date is set to a week after the date of invitation creation.
|
||||
2. The new user receives the invite, typically through their email (although this is not implemented by default within the Medusa backend). The new user has to provide their details and password. The invite can be accepted either using the [Accept Invite endpoint](https://docs.medusajs.com/api/admin#invites_postinvitesinviteaccept) or using the `InviteService`'s `accept` method.
|
||||
3. When the new user accepts the invite, the invitation is validated first to ensure it’s not expired. If it’s not expired, a new user is created using the `UserService`'s [create method](../../references/services/classes/UserService.md#create).
|
||||
|
||||
If an invitation is expired, an existing user can resend the invite either using the Resend Invite endpoint or using the `InviteService`'s resend method. This would generate a new token and reset the expiry date.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to send an invitation email](./backend/send-invite.md)
|
||||
- [How to implement user profiles](./admin/manage-profile.mdx)
|
||||
Reference in new issue
Block a user