chore: merge docs from master to develop (#3650)

* Fix issue on fixed total amount discount when using includes tax (#3472)

The calculation of the fixed discount amount breaks when having includes_tax setting active, due to the line item totals are incorrect and returning everything as 0, thus the totalItemPercentage will be Infinitiy due to the division by a subtotal of 0

* chore: Add missing changeset for @medusajs/medusa

* feat(medusa): Improve performance of Products domain (#3417)

* feat(medusa): Improve product update performances

* fix tests and update

* update mock repo

* improve repo

* cleanup

* fix

* cleanup + bulk emit + unit test fix

* improvements

* improve

* fix unit tests

* fix export

* fix product update handler

* enhance mock repo

* fix import integration

* fix end point tests

* revert mock repo product variant

* fix unit

* cleanup

* cleanup

* address feedback

* fix quotes in tests

* address feedback

* Create new-tips-mate.md

* use types

* chore: Remove integration-tests from changeset

* chore(release): v1.7.14

* chore(docs): Generated Docs Announcement Bar (automated) (#3489)

Co-authored-by: olivermrbl <olivermrbl@users.noreply.github.com>

* fix(medusa): EventBusService.emit using Redis mock (#3491)

* Fix eventBusService.emit using redis mock

* revert gitignore

* enqueuer

* unit test add redis_url

* fix test

* chore(docs): Generated Services Reference (automated) (#3490)

Co-authored-by: olivermrbl <olivermrbl@users.noreply.github.com>

* docs: publish restructure (#3496)

* docs: added features and guides overview page

* added image

* added version 2

* added version 3

* added version 4

* docs: implemented new color scheme

* docs: redesigned sidebar (#3193)

* docs: redesigned navbar for restructure (#3199)

* docs: redesigned footer (#3209)

* docs: redesigned cards (#3230)

* docs: redesigned admonitions (#3231)

* docs: redesign announcement bar (#3236)

* docs: redesigned large cards (#3239)

* docs: redesigned code blocks (#3253)

* docs: redesigned search modal and page (#3264)

* docs: redesigned doc footer (#3268)

* docs: added new sidebars + refactored css and assets (#3279)

* docs: redesigned api reference sidebar

* docs: refactored css

* docs: added code tabs transition

* docs: added new sidebars

* removed unused assets

* remove unusued assets

* Fix deploy errors

* fix incorrect link

* docs: fixed code responsivity + missing icons (#3283)

* docs: changed icons (#3296)

* docs: design fixes to the sidebar (#3297)

* redesign fixes

* docs: small design fixes

* docs: several design fixes after restructure (#3299)

* docs: bordered icon fixes

* docs: desgin fixes

* fixes to code blocks and sidebar scroll

* design adjustments

* docs: restructured homepage (#3305)

* docs: restructured homepage

* design fixes

* fixed core concepts icon

* docs: added core concepts page (#3318)

* docs: restructured homepage

* design fixes

* docs: added core concepts page

* changed text of different components

* docs: added architecture link

* added missing prop for user guide

* docs: added regions overview page (#3327)

* docs: added regions overview

* moved region pages to new structure

* docs: fixed description of regions architecture page

* small changes

* small fix

* docs: added customers overview page (#3331)

* docs: added regions overview

* moved region pages to new structure

* docs: fixed description of regions architecture page

* small changes

* small fix

* docs: added customers overview page

* fix link

* resolve link issues

* docs: updated regions architecture image

* docs: second-iteration fixes (#3347)

* docs: redesigned document

* design fixes

* docs: added products overview page (#3354)

* docs: added carts overview page (#3363)

* docs: added orders overview (#3364)

* docs: added orders overview

* added links in overview

* docs: added vercel redirects

* docs: added soon badge for cards (#3389)

* docs: resolved feedback changes + organized troubleshooting pages (#3409)

* docs: resolved feedback changes

* added extra line

* docs: changed icons for restructure (#3421)

* docs: added taxes overview page (#3422)

* docs: added taxes overview page

* docs: fix sidebar label

* added link to taxes overview page

* fixed link

* docs: fixed sidebar scroll (#3429)

* docs: added discounts overview (#3432)

* docs: added discounts overview

* fixed links

* docs: added gift cards overview (#3433)

* docs: added price lists overview page (#3440)

* docs: added price lists overview page

* fixed links

* docs: added sales channels overview page (#3441)

* docs: added sales overview page

* fixed links

* docs: added users overview (#3443)

* docs: fixed sidebar border height (#3444)

* docs: fixed sidebar border height

* fixed svg markup

* docs: added possible solutions to feedback component (#3449)

* docs: added several overview pages + restructured files (#3463)

* docs: added several overview pages

* fixed links

* docs: added feature flags + PAK overview pages (#3464)

* docs: added feature flags + PAK overview pages

* fixed links

* fix link

* fix link

* fixed links colors

* docs: added strategies overview page (#3468)

* docs: automated upgrade guide (#3470)

* docs: automated upgrade guide

* fixed vercel redirect

* docs: restructured files in docs codebase (#3475)

* docs: restructured files

* docs: fixed eslint exception

* docs: finished restructure loose-ends (#3493)

* fixed uses of backend

* docs: finished loose ends

* eslint fixes

* fixed links

* merged master

* added update instructions for v1.7.12

* docs: fixed discount details (#3499)

* docs: fix trailing slash causing 404 (#3508)

* docs: fix error during navigation (#3509)

* docs: removed the gatsby storefront guide (#3527)

* docs: removed the gatsby storefront guide

* docs: fixed query value

* chore(docs): Removed Docs Announcement Bar (automated) (#3536)

Co-authored-by: shahednasser <shahednasser@users.noreply.github.com>

* fix(medusa): Variant update should include the id for the listeners to be able to identify the entity (#3539)

* fix(medusa): Variant update should include the id for the listeners to be able to identify the entity

* fix unit tests

* Create brave-seahorses-film.md

* docs: fix admin redirects (#3548)

* chore(release): v1.7.15

* chore(docs): Generated Docs Announcement Bar (automated) (#3550)

Co-authored-by: olivermrbl <olivermrbl@users.noreply.github.com>

* chore(docs): Generated Services Reference (automated) (#3551)

Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

* chore: updated READMEs of plugins (#3546)

* chore: updated READMEs of plugins

* added notice to plugins

* docs: added a deploy guide for next.js storefront (#3558)

* docs: added a deploy next.js guide

* docs: fix image zoom

* docs: fixes to next.js deployment guide to vercel (#3562)

* chore(workflows): Enable manual workflow in pre-release mode (#3566)

* chore(docs): Removed Docs Announcement Bar (automated) (#3598)

Co-authored-by: shahednasser <shahednasser@users.noreply.github.com>

* fix(medusa): Rounding issues on line item adjustments (#3446)

* chores(medusa): Attempt to fix discount rounding issues

* add migration

* update entities

* apply multipler factor properly

* fix discount service

* WIP

* fix rounding issues in discounts

* fix some tests

* Exclude raw_discount_total from responses

* fix adjustments

* cleanup response

* fix

* fix draft order integration

* fix order integration

* fix order integration

* address feedback

* fix test

* Create .changeset/polite-llamas-sit.md

* remove comment

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

* chore(workflows): Add release notification (#3629)

---------

Co-authored-by: pepijn-vanvlaanderen <pepijn@webbers.com>
Co-authored-by: olivermrbl <oliver@mrbltech.com>
Co-authored-by: Adrien de Peretti <adrien.deperetti@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: olivermrbl <olivermrbl@users.noreply.github.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
Co-authored-by: shahednasser <shahednasser@users.noreply.github.com>
Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
476 files changed
+14241 -6705

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.md) file in `src/services`. A fulfillment provider is essentially a service that extends the `FulfillmentService`. It requires implementing 4 methods:
1. `getFulfillmentOptions`: used to retrieve available fulfillment options provided by this fulfillment provider.
2. `validateOption`: used to validate the shipping option when it’s being created by the admin.
3. `validateFulfillmentData`: used to validate the shipping method when the customer chooses a shipping option on checkout.
4. `createFulfillment`: used to perform any additional actions when fulfillment is being created for an order.
Also, the fulfillment provider class should have a static property `identifier`. It is the name that will be used to install and refer to the fulfillment provider throughout Medusa.
Fulfillment providers are loaded and installed on the 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.md).
:::
### 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](/api/admin/#tag/Region/operation/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(container)
// 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`](/api/admin/#tag/Shipping-Option/operation/PostShippingOptions).
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`](/api/store/#tag/Cart/operation/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.](/api/admin/#tag/Shipping-Option/operation/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](/api/store/#tag/Cart/operation/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](/api/admin/#tag/Order/operation/PostOrdersOrderReturns) for an order or when the customer [creates a return of their order](/api/store/#tag/Return/operation/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/cab5821f55cfa448c575a20250c918b7fc6835c9/packages/medusa-fulfillment-webshipper) and the [manual fulfillment plugin](https://github.com/medusajs/medusa/tree/cab5821f55cfa448c575a20250c918b7fc6835c9/packages/medusa-fulfillment-manual)
@@ -0,0 +1,654 @@
---
description: 'Learn how to create a payment provider in the Medusa backend. This guide explains the different methods available in a fulfillment provider.'
addHowToData: true
---
# How to Create a Payment Provider
In this document, you’ll learn how to add a Payment Provider to your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.md) first.
## Overview
A Payment Provider is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Provider is Stripe.
By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa.
Adding a Payment Provider is as simple as creating a [service](../../../development/services/create-service.md) file in `src/services`. A Payment Provider is essentially a service that extends `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`.
Payment Provider Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Provider in the Medusa backend.
:::tip
Payment Providers are loaded and installed at the backend startup.
:::
The Payment Provider service is also required to implement the following methods:
1. `createPayment`: Called when a Payment Session for the Payment Provider is to be created.
2. `retrievePayment`: Used to retrieve payment data from the third-party provider, if there’s any.
3. `getStatus`: Used to get the status of a Payment or Payment Session.
4. `updatePayment`: Used to update the Payment Session whenever the cart and its related data are updated.
5. `updatePaymentData`: Used to update the `data` field of Payment Sessions. Specifically called when a request is sent to the [Update Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) endpoint.
6. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted.
7. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created.
8. `getPaymentData`: Used to retrieve the data that should be stored in the `data` field of a new Payment instance after the payment amount has been authorized.
9. `capturePayment`: Used to capture the payment amount of an order or swap.
10. `refundPayment`: Used to refund a payment amount of an order or swap.
11. `cancelPayment`: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled.
:::note
All these methods must be declared async in the Payment Provider Service.
:::
These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed.
![Checkout Flow - Payment](https://res.cloudinary.com/dza7lstvk/image/upload/v1677770739/Medusa%20Docs/Diagrams/checkout-payment_rouet2.png)
---
## Create a Payment Provider
The first step to create a payment provider is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment provider.
For example, create the file `src/services/my-payment.ts` with the following content:
```ts title=src/services/my-payment.ts
import {
AbstractPaymentService, PaymentContext, Data,
Payment, PaymentSession, PaymentSessionStatus,
PaymentSessionData, Cart, PaymentData,
PaymentSessionResponse } from "@medusajs/medusa"
import { EntityManager } from "typeorm"
class MyPaymentService extends AbstractPaymentService {
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
async getPaymentData(
paymentSession: PaymentSession
): Promise<Data> {
throw new Error("Method not implemented.")
}
async updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData> {
throw new Error("Method not implemented.")
}
async createPayment(
context: Cart & PaymentContext
): Promise<PaymentSessionResponse> {
throw new Error("Method not implemented.")
}
async retrievePayment(paymentData: Data): Promise<Data> {
throw new Error("Method not implemented.")
}
async updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData> {
throw new Error("Method not implemented.")
}
async authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{
data: PaymentSessionData;
status: PaymentSessionStatus
}> {
throw new Error("Method not implemented.")
}
async capturePayment(payment: Payment): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async refundPayment(
payment: Payment,
refundAmount: number
): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async cancelPayment(payment: Payment): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async deletePayment(
paymentSession: PaymentSession
): Promise<void> {
throw new Error("Method not implemented.")
}
async getStatus(data: Data): Promise<PaymentSessionStatus> {
throw new Error("Method not implemented.")
}
}
export default MyPaymentService
```
Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`.
Payment Providers must extend `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`.
:::tip
Following the naming convention of Services, the name of the file should be the slug name of the Payment Provider, and the name of the class should be the camel case name of the Payment Provider suffixed with “Service”. In the example above, the name of the file should be `my-payment.js`. You can learn more in the [service documentation](../../../development/services/create-service.md).
:::
### Identifier
As mentioned in the overview, Payment Providers should have a static `identifier` property.
The `PaymentProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Provider is created in the database.
The value of this property will also be used to reference the Payment Provider throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession).
### constructor
You can use the `constructor` of your Payment Provider to have access to different services in Medusa through dependency injection.
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa 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
}
// ...
}
```
### createPayment
This method is called during checkout when [Payment Sessions are initialized](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) to present payment options to the customer. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment.
For example, in Stripe this method is used to initialize a Payment Intent for the customer.
The method receives a context object as a first parameter. This object is of type `PaymentContext` and has the following properties:
```ts
type PaymentContext = {
cart: {
context: Record<string, unknown>
id: string
email: string
shipping_address: Address | null
shipping_methods: ShippingMethod[]
}
currency_code: string
amount: number
resource_id?: string
customer?: Customer
}
```
:::note
Before v1.7.2, the first parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`.
:::
This method must return an object of type `PaymentSessionResponse`. It should have the following properties:
```ts
type PaymentSessionResponse = {
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 provider plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
An example of a minimal implementation of `createPayment`:
```ts
import {
PaymentContext,
PaymentSessionResponse,
} from "@medusajs/medusa"
class MyPaymentService extends AbstractPaymentService {
// ...
async createPayment(
context: Cart & PaymentContext
): Promise<PaymentSessionResponse> {
// 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 Provider Service this method is used to retrieve the payment intent details from Stripe.
This method accepts the `data` field of a Payment Session or a Payment. So, you should make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider.
This method must return an object containing the data from the third-party provider.
An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider:
```ts
import { Data } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async retrievePayment(paymentData: Data): Promise<Data> {
return {}
}
}
```
### getStatus
This method is used to get the status of a Payment or a Payment Session.
Its main usage is in the place order workflow. If the status returned is not `authorized`, then the payment is considered failed, an error will be thrown, and the order will not be placed.
This method accepts the `data` field of the Payment or Payment Session as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary.
This method returns a string that represents the status. The status must be one of the following values:
1. `authorized`: The payment was successfully authorized.
2. `pending`: The payment is still pending. This is the default status of a Payment Session.
3. `requires_more`: The payment requires more actions from the customer. For example, if the customer must complete a 3DS check before the payment is authorized.
4. `error`: If an error occurred with the payment.
5. `canceled`: If the payment was canceled.
An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider:
```ts
import { Data, PaymentSessionStatus } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async getStatus(data: Data): Promise<PaymentSessionStatus> {
return PaymentSessionStatus.AUTHORIZED
}
}
```
:::note
This code block assumes the status is stored in the `data` field as demonstrated in the `createPayment` method.
:::
### updatePayment
This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a [line item is added to the cart](/api/store/#tag/Cart/operation/PostCartsCartLineItems) or when a [shipping method is selected](/api/store/#tag/Cart/operation/PostCartsCartShippingMethod).
:::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 `PaymentContext` and has the following properties:
```ts
type PaymentContext = {
cart: {
context: Record<string, unknown>
id: string
email: string
shipping_address: Address | null
shipping_methods: ShippingMethod[]
}
currency_code: string
amount: number
resource_id?: string
customer?: Customer
}
```
:::note
Before v1.7.2, the second parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`.
:::
You can utilize this method to interact with the third-party provider and update any details regarding the payment if necessary.
This method must return an object of type `PaymentSessionResponse`. It should have the following properties:
```ts
type PaymentSessionResponse = {
update_requests: {
customer_metadata: Record<string, unknown>
}
session_data: Record<string, unknown>
}
```
Where:
- `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.md), the `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
- `update_requests` is an object that can be used to request from the Medusa core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
An example of a minimal implementation of `updatePayment`:
```ts
import {
PaymentSessionData,
Cart,
PaymentContext,
PaymentSessionResponse,
} from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData> {
// prepare data
return {
session_data,
update_requests,
}
}
}
```
### updatePaymentData
This method is used to update the `data` field of a Payment Session. Particularly, it is called when a request is sent to the [Update Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) endpoint. This endpoint receives a `data` object in the body of the request that should be used to update the existing `data` field of the Payment Session.
This method accepts the current `data` field of the Payment Session as the first parameter, and the new `data` field sent in the body request as the second parameter.
You can utilize this method to interact with the third-party provider and make any necessary updates based on the `data` field passed in the body of the request.
This method must return an object that will be stored in the `data` field of the Payment Session.
An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session.
```ts
import { Data, PaymentSessionData } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData> {
return updatedData
}
}
```
### deletePayment
This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:
1. When a request is sent to [delete the Payment Session](/api/store/#tag/Cart/operation/DeleteCartsCartPaymentSessionsSession).
2. When the [Payment Session is refreshed](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionsSession). The Payment Session is deleted so that a newer one is initialized instead.
3. When the Payment Provider is no longer available. This generally happens when the store operator removes it from the available Payment Provider in the admin.
4. When the region of the store is changed based on the cart information and the Payment Provider is not available in the new region.
It accepts the Payment Session as an object for its first parameter.
You can use this method to interact with the third-party provider to delete data related to the Payment Session if necessary.
An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required:
```ts
import { PaymentSession } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async deletePayment(
paymentSession: PaymentSession
): Promise<void> {
return
}
}
```
### authorizePayment
This method is used to authorize payment using the Payment Session for an order. This is called when the [cart is completed](/api/store/#tag/Cart/operation/PostCartsCartComplete) and before the order is created.
This method is also used for authorizing payments of a swap of an order.
The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed, the `authorizePayment` method will be called again to validate that the payment is now fully authorized. So, you should make sure to implement it for this case as well, if necessary.
Once the payment is authorized successfully and the Payment Session status is set to `authorized`, the order can then be placed.
If the payment authorization fails, then an error will be thrown and the order will not be created.
:::note
The payment authorization status is determined using the `getStatus` method as mentioned earlier. If the status is `requires_more` then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not `authorized`, then the payment is considered failed.
:::
This method accepts the Payment Session as an object for its first parameter, and a `context` object as a second parameter. The `context` object contains the following properties:
1. `ip`: The customer’s IP.
2. `idempotency_key`: The [Idempotency Key](../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.
This method must return an object containing the property `status` which is a string that indicates the current status of the payment, and the property `data` which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s `status` and `data` fields respectively.
You can utilize this method to interact with the third-party provider and perform any actions necessary to authorize the payment.
An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider:
```ts
import {
Data,
PaymentSession,
PaymentSessionStatus,
PaymentSessionData,
} from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{
data: PaymentSessionData;
status: PaymentSessionStatus
}> {
return {
status: PaymentSessionStatus.AUTHORIZED,
data: {
id: "test",
},
}
}
}
```
### getPaymentData
After the payment is authorized using `authorizePayment`, a Payment instance will be created. The `data` field of the Payment instance will be set to the value returned from the `getPaymentData` method in the Payment Provider.
This method accepts the Payment Session as an object for its first parameter.
This method must return an object to be stored in the `data` field of the Payment instance. You can either use it as-is or make any changes to it if necessary.
An example of a minimal implementation of `getPaymentData`:
```ts
import { Data, PaymentSession } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async getPaymentData(
paymentSession: PaymentSession
): Promise<Data> {
return paymentSession.data
}
}
```
### capturePayment
This method is used to capture the payment amount of an order. This is typically triggered manually by the store operator from the admin.
This method is also used for capturing payments of a swap of an order.
You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment.
This method accepts the Payment as an object for its first parameter.
This method must return an object that will be stored in the `data` field of the Payment.
An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider:
```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 of an order.
You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment.
This method accepts the Payment as an object for its first parameter, and the amount to refund as a second parameter.
This method must return an object that is stored in the `data` field of the Payment.
An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider:
```ts
import { Data, Payment } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async refundPayment(
payment: Payment,
refundAmount: number
): Promise<Data> {
return {
id: "test",
}
}
}
```
### cancelPayment
This method is used to cancel an order’s payment. This method is typically triggered by one of the following situations:
1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled.
2. If the store operator cancels the order from the admin.
This method is also used for canceling payments of a swap of an order.
You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment.
This method accepts the Payment as an object for its first parameter.
This method must return an object that is stored in the `data` field of the Payment.
An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider:
```ts
import { Data, Payment } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
async cancelPayment(payment: Payment): Promise<Data> {
return {
id: "test",
}
}
}
```
---
## Optional Methods
### retrieveSavedMethods
This method can be added to your Payment Provider service if your third-party provider supports saving the customer’s payment methods. Please note that in Medusa there is no way to save payment methods.
This method is called when a request is sent to [Retrieve Saved Payment Methods](/api/store/#tag/Customer/operation/GetCustomersCustomerPaymentMethods).
This method accepts the customer as an object for its first parameter.
This method returns an array of saved payment methods retrieved from the third-party provider. You have the freedom to shape the items in the array as you see fit since they will be returned as-is for the response to the request.
:::note
If you’re using Medusa’s [Next.js](../../../starters/nextjs-medusa-starter.mdx) storefront starter, note that the presentation of this method is not implemented. You’ll need to implement the UI and pages for this method based on your implementation and the provider you are using.
:::
An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider:
```ts
import { Customer, Data } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService {
// ...
/**
* Fetches a customers saved payment methods
* if they're saved in Stripe.
* @param {object} customer - customer to fetch saved cards for
* @return {Promise<Array<object>>} saved payments methods
*/
async retrieveSavedMethods(
customer: Customer
): Promise<Data[]> {
if (customer.metadata && customer.metadata.stripe_id) {
const methods = await this.stripe_.paymentMethods.list({
customer: customer.metadata.stripe_id,
type: "card",
})
return methods.data
}
return Promise.resolve([])
}
}
```
---
## See Also
- Implementation Examples: [Stripe](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-paypal) payment providers.
@@ -0,0 +1,172 @@
---
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: '/api/store/#tag/Cart',
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 providers 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 Provider',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a fulfillment provider 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={6} items={[
{
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: '/api/store/#tag/Cart',
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={6} items={[
{
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,138 @@
---
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 an ecommerce store related to processing a customer’s payment. It includes the checkout flow and order handling including refunds and swaps.
In Medusa, there are 3 main components in the payment architecture: Payment Provider, Payment Session, and Payment.
1. A **Payment Provider** is a service or method used to capture, authorize, and refund payments, among other functionalities.
2. A **Payment Session** is a session associated with a cart and created during a customer’s checkout flow. It is controlled by the **Payment Provider** to authorize the payment and is used eventually to create a **Payment**.
3. A **Payment** is associated with an order and it represents the amount authorized for the purchase. It is used later for further payment operations such as capturing or refunding payments.
An important part in the Payment architecture to understand is the **Idempotency Key**. It’s a unique value that’s generated for a cart and is used to retry payments during checkout if they fail.
---
## Payment Provider
A Payment Provider in Medusa is a method to handle payments in selected regions. It is not associated with a cart, customer, or order in particular. It provides the necessary implementation to create Payment Sessions and Payments, as well as authorize and capture payments, among other functionalities.
Payment Providers can be integrated with third-party services that handle payment operations such as capturing a payment. An example of a Payment Provider is Stripe.
Payment Providers can also be related to a custom way of handling payment operations. An example of that is Cash on Delivery (COD) payment methods or Medusa’s [manual payment provider plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) which provides a minimal implementation of a payment provider and allows store operators to manually handle order payments.
### How Payment Provider is Created
A Payment Provider is essentially a Medusa [service](../../development/services/create-service.md) with a unique identifier, and it extends the ``AbstractPaymentService` from the core Medusa package `@medusajs/medusa`. It can be created as part of a [plugin](../../development/plugins/overview.mdx), or it can be created just as a service file in your Medusa backend.
As a developer, you will mainly work with the Payment Provider when integrating a payment method in Medusa.
When you run your Medusa backend, the Payment Provider will be registered on your backend if it hasn’t been already.
Once the Payment Provider is added to the backend, the store operator will be able to choose on the [Medusa Admin](../../admin/quickstart.mdx) the payment providers available in a region. These payment providers are shown to the customer at checkout to choose from and use.
:::caution
It’s important to choose a payment provider in the list of payment providers in a region, or else the payment provider cannot be used by customers on checkout.
:::
### PaymentProvider Entity Overview
The [`PaymentProvider`](../../references/entities/classes/PaymentProvider.md) entity only has 2 attributes: `is_installed` to indicate if the payment provider is installed and its value is a boolean; and `id` which is the unique identifier that you define in the Payment Provider service.
---
## Payment Session
Payment Sessions are linked to a customer’s cart. Each Payment Session is associated with a payment provider that is available in the customer cart’s region.
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 Provider available in that region.
During the creation of the Payment Session, the Payment Provider can interact with third-party services for any initialization necessary on their side. For example, when a Payment Session for Stripe is being created, a payment intent associated with the customer can be created with Stripe as well.
Payment Sessions can hold data that is necessary for the customer to complete their payment.
Among the Payment Sessions available only one will be selected based on the customer’s payment provider choice. For example, if the customer sees that they can pay with Stripe or PayPal and chooses Stripe, Stripe’s Payment Session will be the selected Payment Session of that cart.
### PaymentSession Entity Overview
The [`PaymentSession`](../../references/entities/classes/PaymentSession.md) entity belongs to a `Cart`. This is the customer‘s cart that was used for checkout which lead to the creation of the Payment Session.
The `PaymentSession` also belongs to a `PaymentProvider`. This is the Payment Provider that was used to create the Payment Session and that controls it for further actions like authorizing the payment.
The `data` attribute is an object that holds any data required for the Payment Provider to perform payment operations like authorizing or capturing payment. For example, when a Stripe payment session is initialized, the `data` object will hold the payment intent among other data necessary to authorize the payment.
The `is_selected` attribute in the `PaymentSession` entity is a boolean value that indicates whether this Payment Session was selected by the customer to pay for their purchase. Going back to the previous example of having Stripe and PayPal as the available Payment Providers, when the customer chooses Stripe, Stripe’s Payment Session will have `is_selected` set to true whereas PayPal’s Payment Session will have `is_selected` set to false.
The `status` attributes indicates the current status of the Payment Session. It can be one of the following values:
- `authorized`: The payment has been authorized which means the order can be placed successfully.
- `pending`: The payment is still pending further actions. This is usually used when the payment session is initialized.
- `requires_more`: The payment requires additional actions from the customer before the payment can be authorized successfully and the order can be placed. An example of this is payment methods that require 3-D Secure checks.
- `error`: An error was encountered when an authorization was attempted. This status is usually used when an error has been encountered when authorizing the payment with a third-party payment provider.
- `canceled`: The payment has been canceled.
These statuses are important in the checkout flow to determine the current step the customer is at and which action should come next. For example, if there is an attempt to place the order but the status of the Payment Session is not `authorized`, an error will be thrown.
---
## Payment
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 Provider to capture the payment. This is the same case for refunding the amount, canceling the order, or creating a swap.
### Payment Entity Overview
The [`Payment`](../../references/entities/classes/Payment.md) entity belongs to the `Cart` that it was originally created from when the customer’s payment was authorized. It also belongs to an `Order` once it’s placed. Additionally, it belongs to a `PaymentProvider` which is the payment provider that the customer chose on checkout.
In case a `Swap` is created for an order, `Payment` will be associated with that swap to handle payment operations related to it.
Similar to `PaymentSession`, `Payment` has a `data` attribute which is an object that holds any data required to perform further actions with the payment such as capturing the payment.
`Payment` also holds attributes like `amount` which is the amount authorized for payment, and `amount_refunded` which is the amount refunded from the original amount if a refund has been initiated.
Additionally, `Payment` has the `captured_at` date-time attribute which is filled when the payment has been captured, and a `canceled_at` date-time attribute which is filled when the order has been canceled.
---
## Idempotency Key
An Idempotency Key is a unique key associated with a cart. It is generated at the last step of checkout before authorization of the payment is attempted.
That Idempotency Key is then set in the header under the `Idempotency-Key` response header field along with the header field `Access-Control-Expose-Headers` set to `Idempotency-Key`.
If an error occurs or the purchase is interrupted at any step, the client can retry the payment by adding the Idempotency Key of the cart as the `Idempotency-Key` header field in their subsequent requests.
The backend wraps each essential part of the checkout completion in its own step and stores the current step of checkout with its associated Idempotency Key.
If then the request is interrupted for any reason or the payment fails, the client can retry completing the check out using the Idempotency Key, and the flow will continue from the last stored step.
This prevents any payment issues from occurring with the customers and allows for secure retries of failed payments or interrupted connections.
---
## See Also
- [Available Payment Plugins](../../plugins/payment/index.mdx)
- [Create a Payment Provider](./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.md) 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.
![Shipping Architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1677698747/Medusa%20Docs/Diagrams/shipping-architecture_oszxhj.jpg)
---
## 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.md) 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,650 @@
---
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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#cartprovider).
---
## Create a Cart
You can create a cart with the following code snippet:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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](/api/store/#tag/Cart/operation/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" wrapperClassName="code-tabs">
<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 { useGetCart } from "medusa-react"
const Cart = () => {
const { cart, isLoading } = useGetCart(cart_id)
// ...
}
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" wrapperClassName="code-tabs">
<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](/api/store/#tag/Cart/operation/PostCartsCart).
### 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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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](/api/store/#tag/Cart/operation/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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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,599 @@
---
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 provider.
## Overview
A checkout flow is composed of the necessary steps to allow a customer to perform a successful checkout. It’s generally made up of two primary steps: the shipping and payment steps.
This document will take you through the general process of a checkout flow. You should follow along with this document if you’re creating a custom storefront, if you’re adding a custom payment provider, or if you’re just interested in learning more about how checkout works in Medusa.
:::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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#cartprovider).
### Previous Steps
This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](/api/store/#tag/Cart/operation/PostCart) for the customer with at least [one product in it](/api/store/#tag/Cart/operation/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](/api/store/#tag/Cart/operation/PostCartsCart) API endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Shipping-Option/operation/GetShippingOptionsCartId) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 { ShippingOption } from "@medusajs/medusa"
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: ShippingOption) => (
<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](/api/store/#tag/Shipping-Option/operation/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](/api/store/#tag/Cart/operation/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" wrapperClassName="code-tabs">
<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 providers is done differently for each provider, so this section will just show the general steps you should follow when implementing this step.
### Initialize Payment Sessions
When the page opens and before the payment providers are displayed to the customer to choose from, you must initialize the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with.
To initialize the payment sessions, send a `POST` request to the [Initialize Payment Sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) API endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 { PaymentSession } from "@medusajs/medusa"
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 providers</span>
)}
<ul>
{cart?.payment_sessions.map(
(paymentSession: 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 provider they want to complete purchase with, you should select the payment session associated with that payment provider. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 provider in the request's body.
It returns the updated cart, with the selected payment session available under `cart.payment_session`.
:::tip
If you have one payment provider or if only one payment provider is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not.
:::
### Update Payment Session
This step is optional and is only necessary for some payment providers. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Provider to perform payment operations such as capturing payment.
If you need to update that data at any point before the purchase is made, send a request to [Update a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) API endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 provider as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment provider.
It returns the updated cart. You can access the payment session's data on `cart.payment_session.data`.
### Complete Cart
The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment provider first to make sure the authorization is successful when you send the request to complete the cart.
To complete a cart, send a `POST` request to the [Complete a Cart](/api/store/#tag/Cart/operation/PostCartsCartComplete) API endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 handleStartCheckout = () => {
startCheckout.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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## Create Customer Groups
You can create a customer group by sending a request to the Create Customer Group endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Customer-Group/operation/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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## List Customers
You can show a list of customers by sending a request to the [List Customers](/api/admin/#tag/Customer/operation/GetCustomers) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Customer/operation/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](/api/admin/#section/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](/api/admin/#tag/Customer/operation/PostCustomers) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Customer/operation/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](/api/admin/#tag/Customer/operation/PostCustomersCustomer) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Customer/operation/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,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)
+160
View File
@@ -0,0 +1,160 @@
---
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: '#',
label: 'Storefront: Add Customer Profiles',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to implement customer accounts in a storefront.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Admin: Manage Customers',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage customers using Admin APIs.',
isSoon: true,
}
},
{
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: '#',
label: 'Admin: Manage Customer Groups',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage customer groups using Admin APIs.',
isSoon: true,
}
},
{
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: '/api/admin#tag/Customer-Group',
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: '#',
label: 'Architecture: Customer',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Customer Architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Customer Group',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Customer Group Architecture.',
isSoon: true,
}
},
]} />
---
## 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](/api/store/#tag/Customer) 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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#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](/api/store/#tag/Customer/operation/PostCustomers) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Auth/operation/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](/api/store/#tag/Customer/operation/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](/api/store/#tag/Auth/operation/PostAuth) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Auth/operation/DeleteAuth) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/PostCustomersCustomerPasswordToken) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/PostCustomersResetPassword) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/PostCustomersCustomer) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/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](/api/store/#tag/Customer/operation/PostCustomersCustomerAddresses) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/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](/api/store/#tag/Customer/operation/PostCustomersCustomerAddressesAddress) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/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](/api/store/#tag/Customer/operation/DeleteCustomersCustomerAddressesAddress) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/GetCustomersCustomerOrders) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Customer/operation/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](/api/store/#section/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](/api/admin/#tag/Discount/operation/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](/api/admin/#tag/Discount) 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## Create a Discount
You can create a discount by sending a request to the [Create Discount](/api/admin/#tag/Discount/operation/PostDiscounts) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount/operation/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](/api/admin/#tag/Discount/operation/PostDiscountsDiscount) endpoint.
For example, you can update the discount’s description and status by sending the following request:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount/operation/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](/api/admin/#tag/Discount-Condition/operation/PostDiscountsDiscountConditions) endpoint to create a condition in a discount:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount-Condition/operation/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](/api/admin/#tag/Discount-Condition/operation/GetDiscountsDiscountConditionsCondition) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount/operation/PostDiscountsDiscountConditionsCondition) endpoint. You can’t update a condition’s operator.
For example, to update the products in a condition:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount-Condition/operation/DeleteDiscountsDiscountConditionsCondition) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Discount/operation/DeleteDiscountsDiscount) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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)
+108
View File
@@ -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 types 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 types 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 product types 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).
![Discounts Architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1678372360/Medusa%20Docs/Diagrams/discounts_ioivrl.png)
---
## See Also
- [Manage discounts using the admin APIs](./admin/manage-discounts.mdx)
- [Use discounts on the storefront](./storefront/use-discounts-in-checkout.mdx)
+113
View File
@@ -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: '/api/store#tag/Cart/operation/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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#cartprovider).
### Previous Steps
This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](/api/store/#tag/Cart/operation/PostCart) for the customer with at least [one product in it](/api/store/#tag/Cart/operation/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](/api/store/#tag/Cart/operation/PostCartsCart):
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 benefitted 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](/api/store/#tag/Cart/operation/DeleteCartsCartDiscountsDiscount):
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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,815 @@
---
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 the gift card product including retrieving, adding, updating, and deleting it.
- 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## Manage Gift Card Product
This section covers managing the gift card product. There can only be one gift card product in a store. The 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 product by sending a request to the [List Products](/api/admin/#tag/Product/operation/GetProducts) endpoint, but filtering by the `is_giftcard` flag:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 product exists
const giftcard = products[0]
} else {
// no gift card product is 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 product exists
const giftcard = products[0]
} else {
// no gift card product is 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 product.
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](/api/admin/#section/Pagination).
### Create Gift Card Product
You can create only one gift card product in a store. To create a gift card product, send a request to the [Create a Product](/api/admin/#tag/Product/operation/PostProducts) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Product/operation/PostProducts) for available body parameters.
This request returns the created gift card product in the response.
### 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](/api/admin/#tag/Product/operation/PostProductsProduct) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Product/operation/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 the gift card product by sending a request to the [Delete a Product](/api/admin/#tag/Product/operation/DeleteProductsProduct) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Gift-Card/operation/GetGiftCards) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 does not require any parameters. It accepts parameters related to pagination, which you can check out in the [API reference](/api/admin/#tag/Gift-Card/operation/GetGiftCards).
This request returns an array of `gift_cards` and [pagination fields](/api/admin/#section/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](/api/admin/#tag/Gift-Card/operation/PostGiftCards) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Gift-Card/operation/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](/api/admin/#tag/Gift-Card/operation/PostGiftCardsGiftCard) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Gift-Card/operation/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](/api/admin/#tag/Gift-Card/operation/DeleteGiftCardsGiftCard) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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,155 @@
---
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.
### Redis
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
### 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. A store can have only one gift card with 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,323 @@
---
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 the gift card product 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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#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](/api/store/#tag/Product/operation/GetProducts) endpoint, but passing it the `is_giftcard` query parameter:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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 product exists
const giftcard = products[0]
} else {
// no gift card product is created
}
})
```
</TabItem>
<TabItem value="medusa-react" label="Medusa React">
```tsx
import { Product } from "@medusajs/medusa"
import { useProducts } from "medusa-react"
const GiftCard = () => {
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 GiftCard
```
</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 product exists
const giftcard = products[0]
} else {
// no gift card product is 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 product by setting it to `true`. To view other available parameters, check out the [API reference](/api/store/#tag/Product/operation/GetProducts)
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](/api/store/#section/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](/api/store/#tag/Gift-Card/operation/GetGiftCardsCode) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Gift-Card/operation/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](/api/store/#tag/Cart/operation/PostCartsCart) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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,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.
![Order edit flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1677782269/Medusa%20Docs/Diagrams/order-edit_mcnfc3.jpg)
### 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](/api/admin/#tag/OrderEdit).
:::
---
## 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
### 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](/api/admin/#tag/OrderEdit/operation/PostOrderEdits) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/PostOrderEditsEditLineItems) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/PostOrderEditsEditLineItemsLineItem) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/DeleteOrderEditsOrderEditLineItemsLineItem) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/DeleteOrderEditsOrderEditItemChange) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/PostOrderEditsOrderEditRequest) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/OrderEdit/operation/PostOrderEditsOrderEditConfirm) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Payment/operation/PostPaymentsPaymentCapture) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Payment/operation/PostPaymentsPaymentRefunds) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Payment/operation/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,208 @@
---
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.
### Redis
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
### 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)
+357
View File
@@ -0,0 +1,357 @@
---
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: '#',
label: 'Admin: Manage Orders',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage orders using Admin APIs.',
isSoon: true,
}
},
{
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: '#',
label: 'Storefront: Manage Customer Orders',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage orders as a customer.',
isSoon: true,
}
},
]} />
### 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: '#',
label: 'Admin: Manage Draft Orders',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage draft orders using Admin APIs.',
isSoon: true,
}
},
{
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: '/api/admin#tag/Draft-Order',
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: '#',
label: 'Admin: Manage Returns',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage returns using Admin APIs.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Admin: Manage Swaps',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage swaps using Admin APIs.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Admin: Manage Claims',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage claims using Admin APIs.',
isSoon: true,
}
},
{
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: '#',
label: 'Storefront: Create a Return',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a return as a customer.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Storefront: Create a Swap',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a swap as a customer.',
isSoon: true,
}
},
{
type: 'link',
href: '/api/store#tag/Return',
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: '#',
label: 'Architecture: Order',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Order architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Swap',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Swap architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Return',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Return architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Claim',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Claim architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Draft Order',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Draft Order architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Fulfillment',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Fulfillment architecture.',
isSoon: true,
}
},
]} />
---
## 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,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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#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](/api/store/#tag/OrderEdit/operation/GetOrderEditsOrderEdit) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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">
```ts
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](/api/store/#tag/OrderEdit/operation/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 provider and payment session. However, payment collections allow customers to pay in installments or with more than one provider. You can learn more about how to do that using the [batch endpoints of the Payment APIs](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsBatchAuthorize)
:::
If `difference_due` is greater than 0, then additional payment from the customer is required. In this case, you must implement these steps to allow the customer to authorize the payment:
1. Show the customer the available payment providers. These can be retrieved from the details of [the region of the order](/api/store/#tag/Region/operation/GetRegionsRegion).
2. When the customer selects the payment provider, initialize the payment session of that provider in the payment collection. You can do that by sending a request to the [Manage Payment Sessions](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessions) endpoint, passing it the payment collection’s ID as a path parameter, and the payment provider’s ID as a request body parameter:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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">
```ts
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 provider. The [Authorize Payment Session](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsSessionAuthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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">
```ts
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](/api/store/#tag/OrderEdit/operation/PostOrderEditsOrderEditComplete) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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">
```ts
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" wrapperClassName="code-tabs">
<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">
```ts
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.
![Claim Order Flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1677782439/Medusa%20Docs/Diagrams/claim-order-workflow_m6oybo.jpg)
### 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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#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](/api/store/#tag/Auth/operation/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" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>
```ts
medusa.orders.claimOrders({
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" wrapperClassName="code-tabs">
<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)
+247
View File
@@ -0,0 +1,247 @@
---
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 effeciently.
<LargeCardList>
<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['shopping-cart-solid']}
title='Cart and Checkout'
action={{
label: 'Learn more',
href: '/modules/carts-and-checkout/overview'
}}
>
- Accept payments with payment providers 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 unregisterd 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>
<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 heirarchies.
- 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>
<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>
</LargeCardList>
### 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>
<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 providers, 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>
<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>
</LargeCardList>
## 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 Provider',
customProps: {
icon: Icons['credit-card-solid'],
description: 'Create a payment provider.',
},
},
{
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,427 @@
---
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](/api/admin/#tag/Batch-Job), 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.
### Redis
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
### 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
### Created Price List
Before importing the prices, you must have a price list to import them to.
You can use the [Create Price List](/api/admin/#tag/Price-List/operation/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](/api/admin/#tag/Upload/operation/PostUploads) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Batch-Job/operation/PostBatchJobs) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Batch-Job)
@@ -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, retriving 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## 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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/PostPriceListsPriceListPriceList) endpoint.
For example, by sending the following request the end date of the price list will be updated:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/PostPriceListsPriceListPriceList).
---
## Manage Prices
### Add Prices
You can add prices to your price list after creating it using the [Update Prices](/api/admin/#tag/Price-List/operation/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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/PostPriceListsPriceListPricesBatch).
### Delete a Product’s Prices
You can delete all the prices of a product’s variants using the [Delete Product Prices](/api/admin/#tag/Price-List/operation/DeletePriceListsPriceListProductsProductPrices) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/DeletePriceListsPriceListVariantsVariantPrices) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Price-List/operation/DeletePriceListsPriceList) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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
npm run start
```
Then, try out your strategy using any of the [Products](/api/store/#tag/Product) or [Carts](/api/store/#tag/Cart) 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](/api/store/#tag/Product/operation/GetProductsProduct) and [List Product](/api/store/#tag/Product/operation/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,69 @@
---
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` which is used to retrieve a product variant’s price in a given context.
### calculateVariantPrice Method
Medusa uses this method to retrieve a product variant’s price when a product variant or line item is retrieved or manipulated. This includes when other entities that product variants and line items belong to are retrieved, such as products and carts respectively.
This method accepts two parameters:
1. The ID 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`: 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`: 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`: 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`: The ID of the region the customer is using.
- `currency_code`: 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`: The tax rates to be applied. This is only used for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
---
## See Also
- [Override the Price Selection Strategy](./backend/override-price-selection-strategy.md)
@@ -0,0 +1,414 @@
---
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](/api/admin/#tag/Batch-Job), 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.
### Redis
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
### 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)
### 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-1.3.8.csv) to see which format is required for your import.
:::note
If you have Sales Channels enabled on your backend, you must use [this CSV example file](https://medusa-doc-files.s3.amazonaws.com/product-import-sales-channels.csv) instead.
:::
### JS Client
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client](../../../js-client/overview.md) installed and have [created an instance of the client](../../../js-client/overview.md#configuration).
### Medusa React
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## 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](/api/admin/#tag/Upload/operation/PostUploads) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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>' \
-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 products next.
---
## 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](/api/admin/#tag/Batch-Job/operation/PostBatchJobs) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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)
+221
View File
@@ -0,0 +1,221 @@
---
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: '#',
label: 'Admin: Manage Products',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage products using Admin APIs.',
isSoon: true,
}
},
{
type: 'link',
href: '/user-guide/products/manage',
label: 'User Guide: Manage Products',
customProps: {
icon: Icons['users-solid'],
description: 'Manage products in Medusa Admin.'
}
},
{
type: 'link',
href: '#',
label: 'Storefront: Show Products',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to show products in a storefront.',
isSoon: true,
}
},
]} />
### Product Organization
Admins can organize products into collections, tags, types, and more.
Customers can use this organization to filter products while browsing them.
<DocCardList colSize={4} items={[
{
type: 'link',
href: '#',
label: 'Admin: Manage Collections',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage collections using Admin APIs.',
isSoon: true,
}
},
{
type: 'link',
href: '/user-guide/products/collections',
label: 'User Guide: Manage Collections',
customProps: {
icon: Icons['users-solid'],
description: 'Learn how to manage collections in Medusa Admin.'
}
},
{
type: 'link',
href: '#',
label: 'Storefront: Show Collections',
customProps: {
icon: Icons['server-solid'],
description: 'Learn how to show collections in a storefront.',
isSoon: true,
}
},
]} />
### 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: '#',
label: 'Architecture: Product',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Product Architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Collection',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Collection Architecture.',
isSoon: true,
}
},
]} />
---
## 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,547 @@
---
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](/api/admin/#tag/Region), 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](/api/admin/#tag/Region) 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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in the tutorial.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
---
## List Regions
You can retrieve regions available on your backend using the [List Regions](/api/admin/#tag/Region/operation/GetRegions) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/admin/#section/Pagination).
You can also pass filters and other selection query parameters to the request. Check out the [API reference](/api/admin/#tag/Region/operation/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](/api/admin/#tag/Region/operation/PostRegions) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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",
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 provider 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](/api/admin/#tag/Region/operation/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](/api/admin/#tag/Region/operation/PostRegionsRegionCountries) endpoint.
Alternatively, you can update the details of a region using the [Update a Region](/api/admin/#tag/Region/operation/PostRegionsRegion) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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",
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](/api/admin/#tag/Region/operation/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](/api/admin/#tag/Shipping-Option/operation/PostShippingOptions) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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",
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](/api/admin/#tag/Shipping-Option/operation/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](/api/admin/#tag/Shipping-Option).
:::
---
## Delete a Region
You can delete a region by sending a request to the [Delete a Region](/api/admin/#tag/Region/operation/DeleteRegionsRegion) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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,157 @@
---
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 providers, 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: '#',
label: 'Admin: Manage Currencies',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage currencies using the Admin APIs.',
isSoon: true
}
},
{
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 providers 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: '/api/store/#tag/Region',
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: '#',
label: 'Architecture: Currency',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Currency Architecture.',
isSoon: true,
}
},
]} />
---
## 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 provider for region A, and a different payment provider for region B. Customers can then use the payment provider 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 providers.
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 providers, then you can include more countries in the same region.
If you serve customers in different countries that have at least one different configuration, such as different payment providers, you need to create a new region for those countries.
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.
![Regions Relations Overview](https://res.cloudinary.com/dza7lstvk/image/upload/v1677487695/Medusa%20Docs/Diagrams/regions-architecture_v50h9d.jpg)
### 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 provider. A payment provider 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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#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](/api/store/#tag/Region/operation/GetRegions) endpoint:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#tag/Product/operation/GetProducts) or [Get a Product](/api/store/#tag/Product/operation/GetProductsProduct) endpoints.
For example:
<Tabs groupId="request-type" wrapperClassName="code-tabs">
<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](/api/store/#section/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" wrapperClassName="code-tabs">
<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](/api/store/#tag/Cart/operation/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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
### Authenticated Admin User
You must be an authenticated admin user before following along with the steps in this guide.
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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.](/api/admin/#tag/Sales-Channel/operation/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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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](/api/admin/#tag/Sales-Channel/operation/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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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 storefront](../../../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.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
For requests that use the cart, it's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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" wrapperClassName="code-tabs">
<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.
@@ -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](/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](/api/store/#tag/Product/operation/GetProductsProduct) or a [list of products is retrieved](/api/store/#tag/Product/operation/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](/api/store/#tag/Cart).
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](/api/store/#tag/Shipping-Option).
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](/api/store/#tag/Cart).
An order’s totals, including its taxes, are calculated and retrieved whenever the order is retrieved both on the [storefront](/api/store/#tag/Order) and on the [admin](/api/admin/#tag/Order).
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)
+197
View File
@@ -0,0 +1,197 @@
---
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={6} items={[
{
type: 'link',
href: '#',
label: 'Backend: Create Tax Provider',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a tax provider.',
isSoon: true
}
},
{
type: 'link',
href: '#',
label: 'Admin: Manage Tax Providers',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage a taxes using Admin APIs.',
isSoon: true
}
},
]} />
<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={6} items={[
{
type: 'link',
href: '#',
label: 'Admin: Manage Tax Rates',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage tax rates using Admin APIs.',
isSoon: true
}
},
{
type: 'link',
href: '#',
label: 'Admin: Manage Tax Overrides',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage tax overrides using Admin APIs.',
isSoon: true
}
},
{
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: '#',
label: 'Architecture: Tax',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Tax Architecture.',
isSoon: true,
}
},
{
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](/api/store/#tag/Cart/operation/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.md#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.md#using-your-custom-service) in this documentation.
:::
+134
View File
@@ -0,0 +1,134 @@
---
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 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: '#',
label: 'Admin: Manage Users',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage users using Admin APIs.',
isSoon: true
}
},
{
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: '#',
label: 'Admin: Manage Profile',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage a user profile using Admin APIs.',
isSoon: true
}
},
{
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: '#',
label: 'Admin: Manage Invites',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to manage invites using Admin APIs.',
isSoon: true
}
},
{
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: '#',
label: 'Backend: Send Invite',
customProps: {
icon: Icons['users-solid'],
description: 'Learn how to send invites in the backend.',
isSoon: true
}
},
]} />
---
## Understand the Architecture
Learn how user-related entities and concepts are built, their relation to other modules, and more.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '#',
label: 'Architecture: User',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the User Architecture.',
isSoon: true,
}
},
{
type: 'link',
href: '#',
label: 'Architecture: Invites',
customProps: {
icon: Icons['circle-stack-solid'],
description: 'Learn about the Invite Architecture.',
isSoon: true,
}
},
]} />