api-ref: custom API reference (#4770)
* initialized next.js project * finished markdown sections * added operation schema component * change page metadata * eslint fixes * fixes related to deployment * added response schema * resolve max stack issue * support for different property types * added support for property types * added loading for components * added more loading * type fixes * added oneOf type * removed console * fix replace with push * refactored everything * use static content for description * fixes and improvements * added code examples section * fix path name * optimizations * fixed tag navigation * add support for admin and store references * general enhancements * optimizations and fixes * fixes and enhancements * added search bar * loading enhancements * added loading * added code blocks * added margin top * add empty response text * fixed oneOf parameters * added path and query parameters * general fixes * added base path env variable * small fix for arrays * enhancements * design enhancements * general enhancements * fix isRequired * added enum values * enhancements * general fixes * general fixes * changed oas generation script * additions to the introduction section * added copy button for code + other enhancements * fix response code block * fix metadata * formatted store introduction * move sidebar logic to Tags component * added test env variables * fix code block bug * added loading animation * added expand param + loading * enhance operation loading * made responsive + improvements * added loading provider * fixed loading * adjustments for small devices * added sidebar label for endpoints * added feedback component * fixed analytics * general fixes * listen to scroll for other headings * added sample env file * update api ref files + support new fields * fix for external docs link * added new sections * fix last item in sidebar not showing * move docs content to www/docs * change redirect url * revert change * resolve build errors * configure rewrites * changed to environment variable url * revert changing environment variable name * add environment variable for API path * fix links * fix tailwind settings * remove vercel file * reconfigured api route * move api page under api * fix page metadata * fix external link in navigation bar * update api spec * updated api specs * fixed google lint error * add max-height on request samples * add padding before loading * fix for one of name * fix undefined types * general fixes * remove response schema example * redesigned navigation bar * redesigned sidebar * fixed up paddings * added feedback component + report issue * fixed up typography, padding, and general styling * redesigned code blocks * optimization * added error timeout * fixes * added indexing with algolia + fixes * fix errors with algolia script * redesign operation sections * fix heading scroll * design fixes * fix padding * fix padding + scroll issues * fix scroll issues * improve scroll performance * fixes for safari * optimization and fixes * fixes to docs + details animation * padding fixes for code block * added tab animation * fixed incorrect link * added selection styling * fix lint errors * redesigned details component * added detailed feedback form * api reference fixes * fix tabs * upgrade + fixes * updated documentation links * optimizations to sidebar items * fix spacing in sidebar item * optimizations and fixes * fix endpoint path styling * remove margin * final fixes * change margin on small devices * generated OAS * fixes for mobile * added feedback modal * optimize dark mode button * fixed color mode useeffect * minimize dom size * use new style system * radius and spacing design system * design fixes * fix eslint errors * added meta files * change cron schedule * fix docusaurus configurations * added operating system to feedback data * change content directory name * fixes to contribution guidelines * revert renaming content * added api-reference to documentation workflow * fixes for search * added dark mode + fixes * oas fixes * handle bugs * added code examples for clients * changed tooltip text * change authentication to card * change page title based on selected section * redesigned mobile navbar * fix icon colors * fix key colors * fix medusa-js installation command * change external regex in algolia * change changeset * fix padding on mobile * fix hydration error * update depedencies
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
---
|
||||
description: 'Learn how to create a fulfillment provider in the Medusa backend. This guide explains the different methods in the fulfillment provider.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Add a Fulfillment Provider
|
||||
|
||||
In this document, you’ll learn how to add a fulfillment provider to a Medusa backend. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to [check out the overview first](../shipping.md).
|
||||
|
||||
## Overview
|
||||
|
||||
A fulfillment provider is the shipping provider used to fulfill orders and deliver them to customers. An example of a fulfillment provider is FedEx.
|
||||
|
||||
By default, a Medusa Backend has a `manual` fulfillment provider which has minimal implementation. It allows you to accept orders and fulfill them manually. However, you can integrate any fulfillment provider into Medusa, and your fulfillment provider can interact with third-party shipping providers.
|
||||
|
||||
Adding a fulfillment provider is as simple as creating one [service](../../../development/services/create-service.mdx) file in `src/services`. A fulfillment provider is essentially a service that extends the `FulfillmentService`. It requires implementing 4 methods:
|
||||
|
||||
1. `getFulfillmentOptions`: used to retrieve available fulfillment options provided by this fulfillment provider.
|
||||
2. `validateOption`: used to validate the shipping option when it’s being created by the admin.
|
||||
3. `validateFulfillmentData`: used to validate the shipping method when the customer chooses a shipping option on checkout.
|
||||
4. `createFulfillment`: used to perform any additional actions when fulfillment is being created for an order.
|
||||
|
||||
Also, the fulfillment provider class should have a static property `identifier`. It is the name that will be used to install and refer to the fulfillment provider throughout Medusa.
|
||||
|
||||
Fulfillment providers are loaded and installed on the backend startup.
|
||||
|
||||
---
|
||||
|
||||
## Create a Fulfillment Provider
|
||||
|
||||
The first step is to create a JavaScript or TypeScript file under `src/services`. For example, create the file `src/services/my-fulfillment.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-fulfillment.ts
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
|
||||
}
|
||||
|
||||
export default MyFulfillmentService
|
||||
```
|
||||
|
||||
Fulfillment provider services must extend the `FulfillmentService` class imported from `medusa-interfaces`.
|
||||
|
||||
:::note
|
||||
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the fulfillment provider, and the name of the class should be the camel case name of the fulfillment provider suffixed with “Service”. You can learn more in the [service documentation](../../../development/services/create-service.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### Identifier
|
||||
|
||||
As mentioned in the overview, fulfillment providers should have a static `identifier` property.
|
||||
|
||||
The `FulfillmentProvider` entity has 2 properties: `identifier` and `is_installed`. The `identifier` property in the class will be used when the fulfillment provider is created in the database.
|
||||
|
||||
The value of this property will also be used to reference the fulfillment provider throughout Medusa. For example, it is used to [add a fulfillment provider](https://docs.medusajs.com/api/admin#regions_postregionsregionfulfillmentproviders) to a region.
|
||||
|
||||
```ts
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
static identifier = "my-fulfillment"
|
||||
}
|
||||
|
||||
export default MyFulfillmentService
|
||||
```
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your fulfillment provider to have access to different services in Medusa through dependency injection. You can access any services you create in Medusa in the first parameter.
|
||||
|
||||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
constructor(container, options) {
|
||||
super()
|
||||
// you can access options here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getFulfillmentOptions
|
||||
|
||||
When the admin is creating shipping options available for customers during checkout, they choose one of the fulfillment options provided by underlying fulfillment providers.
|
||||
|
||||
For example, if you’re integrating UPS as a fulfillment provider, you might support two fulfillment options: UPS Express Shipping and UPS Access Point.
|
||||
|
||||
These fulfillment options are defined in the `getFulfillmentOptions` method. This method should return an array of options.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "my-fulfillment",
|
||||
},
|
||||
{
|
||||
id: "my-fulfillment-dynamic",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the admin chooses one of those fulfillment options, the data of the chosen fulfillment option is stored in the `data` property of the shipping option created. This property is used to add any additional data you need to fulfill the order with the third-party provider.
|
||||
|
||||
For that reason, the fulfillment option doesn't have any required structure and can be of any format that works for your integration.
|
||||
|
||||
### validateOption
|
||||
|
||||
Once the admin creates the shipping option, the data will be validated first using this method in the underlying fulfillment provider of that shipping option. This method is called when a `POST` request is sent to [`/admin/shipping-options`](https://docs.medusajs.com/api/admin#shipping-options_getshippingoptions).
|
||||
|
||||
This method accepts the `data` object that is sent in the body of the request. You can use this data to validate the shipping option before it is saved.
|
||||
|
||||
This method returns a boolean. If the result is false, an error is thrown and the shipping option will not be saved.
|
||||
|
||||
For example, you can use this method to ensure that the `id` in the `data` object is correct:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async validateOption(data) {
|
||||
return data.id == "my-fulfillment"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your fulfillment provider does not need to run any validation, you can simply return `true`.
|
||||
|
||||
### validateFulfillmentOption
|
||||
|
||||
When the customer chooses a shipping option on checkout, the shipping option and its data are validated before the shipping method is created.
|
||||
|
||||
`validateFulfillmentOption` is called when a `POST` request is sent to [`/carts/:id/shipping-methods`](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
|
||||
This method accepts three parameters:
|
||||
|
||||
1. The shipping option data.
|
||||
2. The `data` object passed in the body of the request.
|
||||
3. The customer’s cart data.
|
||||
|
||||
You can use these parameters to validate the chosen shipping option. For example, you can check if the `data` object includes all data needed to fulfill the shipment later on.
|
||||
|
||||
If any of the data is invalid, you can throw an error. This error will stop Medusa from creating a shipping method and the error message will be returned as a result to the endpoint.
|
||||
|
||||
If everything is valid, this method must return a value that will be stored in the `data` property of the shipping method to be created. So, make sure the value you return contains everything you need to fulfill the shipment later on.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
async validateFulfillmentData(optionData, data, cart) {
|
||||
if (data.id !== "my-fulfillment") {
|
||||
throw new Error("invalid data")
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### createFulfillment
|
||||
|
||||
After an order is placed, it can be fulfilled either manually by the admin or using automation.
|
||||
|
||||
This method gives you access to the fulfillment being created as well as other details in case you need to perform any additional actions with the third-party provider.
|
||||
|
||||
This method accepts four parameters:
|
||||
|
||||
1. The data of the shipping method associated with the order.
|
||||
2. An array of items in the order to be fulfilled. The admin can choose all or some of the items to fulfill.
|
||||
3. The data of the order
|
||||
4. The data of the fulfillment being created.
|
||||
|
||||
You can use the `data` property in the shipping method (first parameter) to access the data specific to the shipping option. This is based on your implementation of previous methods.
|
||||
|
||||
Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
createFulfillment(
|
||||
methodData,
|
||||
fulfillmentItems,
|
||||
fromOrder,
|
||||
fulfillment
|
||||
) {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
This method is also used to create claims and swaps. The fulfillment object has the fields `claim_id`, `swap_id`, and `order_id`. You can check which isn’t null to determine what type of fulfillment is being created.
|
||||
|
||||
:::
|
||||
|
||||
### Useful Methods
|
||||
|
||||
The above-detailed methods are the required methods for every fulfillment provider. However, there are additional methods that you can use in your fulfillment provider to customize it further or add additional features.
|
||||
|
||||
#### canCalculate
|
||||
|
||||
This method validates whether a shipping option is calculated dynamically or flat rate. It is called if the `price_type` of the shipping option being created is set to `calculated`.
|
||||
|
||||
If this method returns `true`, that means that the price should be calculated dynamically. The `amount` property of the shipping option will then be set to `null`. The amount will be created later when the shipping method is created on checkout using the `calculatePrice` method (explained next).
|
||||
|
||||
If the method returns `false`, an error is thrown as it means the selected shipping option can only be chosen if the price type is set to `flat_rate`.
|
||||
|
||||
This method receives as a parameter the `data` object sent with the request that [creates the shipping option.](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions) You can use this data to determine whether the shipping option should be calculated or not. This is useful if the fulfillment provider you are integrating has both flat rate and dynamically priced fulfillment options.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
canCalculate(data) {
|
||||
return data.id === "my-fulfillment-dynamic"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### calculatePrice
|
||||
|
||||
This method is called on checkout when the shipping method is being created if the `price_type` of the selected shipping option is `calculated`.
|
||||
|
||||
This method receives three parameters:
|
||||
|
||||
1. The `data` parameter of the selected shipping option.
|
||||
2. The `data` parameter sent with [the request](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
3. The customer’s cart data.
|
||||
|
||||
If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
calculatePrice() {
|
||||
// leave empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Otherwise, you can use it to calculate the price with a custom logic. For example:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
calculatePrice(optionData, data, cart) {
|
||||
return cart.items.length * 1000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### createReturn
|
||||
|
||||
Fulfillment providers can also be used to return products. A shipping option can be used for returns if the `is_return` property is `true` or if an admin creates a Return Shipping Option from the settings.
|
||||
|
||||
This method is called when the admin [creates a return request](https://docs.medusajs.com/api/admin#orders_postordersorderreturns) for an order or when the customer [creates a return of their order](https://docs.medusajs.com/api/store#returns_postreturns).
|
||||
|
||||
It gives you access to the return being created in case you need to perform any additional actions with the third-party provider.
|
||||
|
||||
It receives the return created as a parameter. The value it returns is set to the `shipping_data` of the return instance.
|
||||
|
||||
This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
createReturn(returnOrder) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### cancelFulfillment
|
||||
|
||||
This method is called when a fulfillment is cancelled by the admin. This fulfillment can be for an order, a claim, or a swap.
|
||||
|
||||
It gives you access to the fulfillment being canceled in case you need to perform any additional actions with your third-party provider.
|
||||
|
||||
This method receives the fulfillment being cancelled as a parameter.
|
||||
|
||||
This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment:
|
||||
|
||||
```ts
|
||||
class MyFulfillmentService extends FulfillmentService {
|
||||
// ...
|
||||
cancelFulfillment(fulfillment) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- Example Implementations: [Webshipper plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-webshipper) and the [manual fulfillment plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-manual)
|
||||
@@ -0,0 +1,603 @@
|
||||
---
|
||||
description: 'Learn how to create a payment processor in the Medusa. This guide explains the different methods available in a payment processor.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Payment Processor
|
||||
|
||||
In this document, you’ll learn how to create a Payment Processor in your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.md) first.
|
||||
|
||||
:::note
|
||||
|
||||
Before v1.8 of Medusa, this guide explained how to create a payment provider. Payment Providers are now considered legacy and are deprecated. Moving forward, it's recommended to create a Payment Processor that implements the Payment Processor API.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
A Payment Processor is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Processor is Stripe.
|
||||
|
||||
By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa.
|
||||
|
||||
Adding a Payment Processor is as simple as creating a [service](../../../development/services/create-service.mdx) file in `src/services`. A Payment Processor is essentially a service that extends `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`.
|
||||
|
||||
Payment Processor Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Processor in the Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Payment Processors are loaded and installed at the server startup. If not already saved, they're saved in the database and are represented by the `PaymentProvider` entity.
|
||||
|
||||
:::
|
||||
|
||||
The Payment Processor is also required to implement the following methods:
|
||||
|
||||
1. `initiatePayment`: Called when a Payment Session for the Payment Provider is to be created.
|
||||
2. `retrievePayment`: Used to retrieve payment session data, which can be retrieved from a third-party provider.
|
||||
3. `getPaymentStatus`: Used to get the status of a Payment or Payment Session.
|
||||
4. `updatePayment`: Used to update the Payment Session whenever the cart and its related data are updated.
|
||||
5. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted. For example, you can cancel the payment with the third-party provider.
|
||||
6. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created.
|
||||
7. `capturePayment`: Used to capture the payment amount of an order or swap.
|
||||
8. `refundPayment`: Used to refund a payment amount of an order or swap.
|
||||
9. `cancelPayment`: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled.
|
||||
|
||||
:::note
|
||||
|
||||
All these methods must be declared async in the Payment Processor.
|
||||
|
||||
:::
|
||||
|
||||
These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Create a Payment Processor
|
||||
|
||||
The first step to create a payment processor is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment processor, and it should be in snake case.
|
||||
|
||||
For example, create the file `src/services/my-payment-processor.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-payment-processor.ts
|
||||
import {
|
||||
AbstractPaymentProcessor,
|
||||
PaymentProcessorContext,
|
||||
PaymentProcessorError,
|
||||
PaymentProcessorSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentProcessor extends AbstractPaymentProcessor {
|
||||
static identifier = "my-payment"
|
||||
|
||||
async capturePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async authorizePayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async refundPayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
void |
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPaymentProcessor
|
||||
```
|
||||
|
||||
Where `MyPaymentProcessor` is the name of your Payment Processor service.
|
||||
|
||||
Payment Processors must extend `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`.
|
||||
|
||||
:::tip
|
||||
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the Payment Processor, and the name of the class should be the camel case name of the Payment Processors suffixed with “Service”. In the example above, the name of the file should be `my-payment.ts`. You can learn more in the [service documentation](../../../development/services/create-service.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### identifier
|
||||
|
||||
As mentioned in the overview, Payment Processors should have a static `identifier` property.
|
||||
|
||||
The `PaymentProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Processor is created in the database.
|
||||
|
||||
The value of this property will also be used to reference the Payment Processor throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession).
|
||||
|
||||
The identifier can be retrieved using the `getIdentifier` method, which is defined in `AbstractPaymentProcessor`.
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your Payment Processor to have access to different services in Medusa through [dependency injection](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re creating your Payment Processor as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter:
|
||||
|
||||
```ts
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
constructor(container, options) {
|
||||
super(container)
|
||||
// you can access options here
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### PaymentProcessorError
|
||||
|
||||
Before diving into the methods you'll need to implement, you'll notice that part of the expected return signature of these method includes `PaymentProcessorError`. This is an interface of the following definition:
|
||||
|
||||
```ts
|
||||
interface PaymentProcessorError {
|
||||
error: string
|
||||
code?: string
|
||||
detail?: any
|
||||
}
|
||||
```
|
||||
|
||||
While implementing the following methods, if you need to inform the Medusa core that an error occurred at a certain stage, return an object having the attributes defined in the `PaymentProcessorError` interface.
|
||||
|
||||
For example, the Stripe payment processor has the following method to create the error object, which is used within other methods:
|
||||
|
||||
```ts
|
||||
abstract class StripeBase extends AbstractPaymentProcessor {
|
||||
// ...
|
||||
protected buildError(
|
||||
message: string,
|
||||
e: Stripe.StripeRawError | PaymentProcessorError | Error
|
||||
): PaymentProcessorError {
|
||||
return {
|
||||
error: message,
|
||||
code: "code" in e ? e.code : "",
|
||||
detail: isPaymentProcessorError(e)
|
||||
? `${e.error}${EOL}${e.detail ?? ""}`
|
||||
: "detail" in e
|
||||
? e.detail
|
||||
: e.message ?? "",
|
||||
}
|
||||
}
|
||||
|
||||
// used in other methods
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
try {
|
||||
// ...
|
||||
} catch (e) {
|
||||
return this.buildError(
|
||||
"An error occurred in retrievePayment",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### initiatePayment
|
||||
|
||||
This method is called either if a region has only one payment provider enabled or when [a Payment Session is selected](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession), which occurs when the customer selects their preferred payment method during checkout. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment.
|
||||
|
||||
For example, in Stripe this method is used to create a Payment Intent for the customer.
|
||||
|
||||
The method receives a context object as a first parameter. This object is of type `PaymentProcessorContext` and has the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorContext = {
|
||||
billing_address?: Address | null
|
||||
email: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
resource_id: string
|
||||
customer?: Customer
|
||||
context: Record<string, unknown>
|
||||
paymentSessionData: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
This method must return an object of type `PaymentProcessorSessionResponse`. It should have the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorSessionResponse = {
|
||||
update_requests?: {
|
||||
customer_metadata?: Record<string, unknown>
|
||||
}
|
||||
session_data: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.md), the `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
|
||||
- `update_requests` is an object that can be used to pass data from the Payment Processor plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
|
||||
|
||||
An example of a minimal implementation of `initiatePayment`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
PaymentContext,
|
||||
PaymentSessionResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
update_requests,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### retrievePayment
|
||||
|
||||
This method is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Processor this method is used to retrieve the payment intent details from Stripe.
|
||||
|
||||
This method accepts the `data` field of a Payment Session. So, you should make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider.
|
||||
|
||||
This method must return an object containing the data from the third-party provider.
|
||||
|
||||
An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getPaymentStatus
|
||||
|
||||
This method is used to get the status of a Payment or a Payment Session.
|
||||
|
||||
Its main usage is in the place order and create swap workflows. If the status returned is not `authorized`, then the payment is considered failed and an error will be thrown, stopping the task from completion.
|
||||
|
||||
This method accepts the `data` field of a Payment as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary.
|
||||
|
||||
This method returns a string that represents the status. This string can be from the enum `PaymentSessionStatus` which can be imported from `@medusajs/medusa`. The status must be one of the following values:
|
||||
|
||||
1. `authorized`: The payment was successfully authorized.
|
||||
2. `pending`: The payment is still pending. This is the default status of a Payment Session.
|
||||
3. `requires_more`: The payment requires more actions from the customer. For example, if the customer must complete a 3DS check before the payment is authorized.
|
||||
4. `error`: If an error occurred with the payment.
|
||||
5. `canceled`: If the payment was canceled.
|
||||
|
||||
An example of a minimal implementation of `getPaymentStatus` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
return PaymentSessionStatus.AUTHORIZED
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### updatePayment
|
||||
|
||||
This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a [line item is added to the cart](https://docs.medusajs.com/api/store#carts_postcartscartlineitems) or when a [shipping method is selected](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod).
|
||||
|
||||
:::tip
|
||||
|
||||
A line item refers to a product in the cart.
|
||||
|
||||
:::
|
||||
|
||||
It accepts the `data` field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type `PaymentProcessorContext` and has the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorContext = {
|
||||
billing_address?: Address | null
|
||||
email: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
resource_id: string
|
||||
customer?: Customer
|
||||
context: Record<string, unknown>
|
||||
paymentSessionData: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
You can utilize this method to interact with the third-party provider and update any details regarding the payment if necessary.
|
||||
|
||||
This method must return an object of type `PaymentSessionResponse`. It should have the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorSessionResponse = {
|
||||
update_requests?: {
|
||||
customer_metadata?: Record<string, unknown>
|
||||
}
|
||||
session_data: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
These are the same fields explained in the [initiatePayment](#initiatepayment) section.
|
||||
|
||||
An example of a minimal implementation of `updatePayment`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
PaymentSessionData,
|
||||
Cart,
|
||||
PaymentContext,
|
||||
PaymentSessionResponse,
|
||||
} from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
void |
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
update_requests,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### deletePayment
|
||||
|
||||
This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:
|
||||
|
||||
1. When a request is sent to [delete the Payment Session](https://docs.medusajs.com/api/store#carts_deletecartscartpaymentsessionssession).
|
||||
2. When the [Payment Session is refreshed](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessionssession). The Payment Session is deleted so that a newer one is initialized instead.
|
||||
3. When the Payment Processor is no longer available. This generally happens when the store operator removes it from the available Payment Processor in the admin.
|
||||
4. When the region of the store is changed based on the cart information and the Payment Processor is not available in the new region.
|
||||
|
||||
It accepts the `data` field of the payment session for its first parameter.
|
||||
|
||||
You can use this method to interact with the third-party provider to delete data related to the Payment Session if necessary.
|
||||
|
||||
An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required:
|
||||
|
||||
```ts
|
||||
import { PaymentSession } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return paymentSessionData
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### authorizePayment
|
||||
|
||||
This method is used to authorize payment using the Payment Session of an order. This is called when the [cart is completed](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) and before the order is created.
|
||||
|
||||
This method is also used for authorizing payments of a swap of an order and when authorizing sessions in a payment collection.
|
||||
|
||||
The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed, the `authorizePayment` method will be called again to validate that the payment is now fully authorized. So, you should make sure to implement it for this case as well, if necessary.
|
||||
|
||||
Once the payment is authorized successfully and the Payment Session status is set to `authorized`, the order can then be placed.
|
||||
|
||||
If the payment authorization fails, then an error will be thrown and the order will not be created.
|
||||
|
||||
:::note
|
||||
|
||||
The payment authorization status is determined using the `getPaymentStatus` method as mentioned earlier. If the status is `requires_more` then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not `authorized`, then the payment is considered failed.
|
||||
|
||||
:::
|
||||
|
||||
This method accepts the `data` field of a payment session for its first parameter, and a `context` object as a second parameter. The `context` object may contain the following properties:
|
||||
|
||||
1. `ip`: The customer’s IP.
|
||||
2. `idempotency_key`: The [Idempotency Key](../payment.md#idempotency-key) that is associated with the current cart. It is useful when retrying payments, retrying checkout at a failed point, or for payments that require additional actions from the customer.
|
||||
3. `cart_id`: The ID of a cart. This is only during operations like placing an order or creating a swap.
|
||||
|
||||
This method must return an object containing the following properties:
|
||||
|
||||
- `status` which is a string that indicates the current status of the payment.
|
||||
- `data` which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s `status` and `data` fields respectively.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to authorize the payment.
|
||||
|
||||
An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider:
|
||||
|
||||
```ts
|
||||
import {
|
||||
Data,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
PaymentSessionData,
|
||||
} from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async authorizePayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
return {
|
||||
status: PaymentSessionStatus.AUTHORIZED,
|
||||
data: {
|
||||
id: "test",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### capturePayment
|
||||
|
||||
This method is used to capture the payment amount of an order. This is typically triggered manually by the store operator from the admin.
|
||||
|
||||
This method is also used for capturing payments of a swap of an order, or when the [Capture Payment](https://docs.medusajs.com/api/admin#payments_postpaymentspaymentcapture) endpoint is called.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment.
|
||||
|
||||
This method accepts the `data` field of the Payment for its first parameter.
|
||||
|
||||
This method must return an object that will be stored in the `data` field of the Payment.
|
||||
|
||||
An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async capturePayment(payment: Payment): Promise<Data> {
|
||||
return {
|
||||
status: "captured",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### refundPayment
|
||||
|
||||
This method is used to refund an order’s payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total order amount or part of it.
|
||||
|
||||
This method is also used for refunding payments of a swap or a claim of an order, or when the [Refund Payment](https://docs.medusajs.com/api/admin#payments_postpaymentspaymentrefunds) endpoint is called.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment.
|
||||
|
||||
This method accepts the `data` field of a Payment for its first parameter, and the amount to refund as a second parameter.
|
||||
|
||||
This method must return an object that is stored in the `data` field of the Payment.
|
||||
|
||||
An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async refundPayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### cancelPayment
|
||||
|
||||
This method is used to cancel an order’s payment. This method is typically triggered by one of the following situations:
|
||||
|
||||
1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled.
|
||||
2. If the store operator cancels the order from the admin.
|
||||
3. When the payment of an order's swap is canceled.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment.
|
||||
|
||||
This method accepts the `data` field of the Payment for its first parameter.
|
||||
|
||||
An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, Payment } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- Implementation Examples: [Stripe](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-paypal) Payment Processors.
|
||||
- [Implement checkout flow on the storefront](../storefront/implement-checkout-flow.mdx).
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
description: 'Learn how to override the cart completion strategy to implement your custom cart completion strategy.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override Cart Completion Strategy
|
||||
|
||||
In this document, you’ll learn how to override the cart completion strategy.
|
||||
|
||||
:::note
|
||||
|
||||
This guide only explains how to override the cart completion strategy. It’s highly recommended to first understand how Medusa implements the cart completion strategy as explained [here](../cart.md#cart-completion-process).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Strategy Class
|
||||
|
||||
Create a TypeScript or JavaScript file in `src/strategies` of your Medusa backend project with a class that extends the `AbstractCartCompletionStrategy` class:
|
||||
|
||||
```ts title=src/strategies/cart-completion.ts
|
||||
import {
|
||||
AbstractCartCompletionStrategy,
|
||||
CartCompletionResponse,
|
||||
IdempotencyKey } from "@medusajs/medusa"
|
||||
import {
|
||||
RequestContext,
|
||||
} from "@medusajs/medusa/dist/types/request"
|
||||
|
||||
class CartCompletionStrategy
|
||||
extends AbstractCartCompletionStrategy {
|
||||
|
||||
complete(
|
||||
cartId: string,
|
||||
idempotencyKey: IdempotencyKey,
|
||||
context: RequestContext
|
||||
): Promise<CartCompletionResponse> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CartCompletionStrategy
|
||||
```
|
||||
|
||||
The class includes the `complete` method defined as abstract in `AbstractCartCompletionStrategy`. At the moment, the method only throws an error.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title=src/strategies/cart-completion.ts
|
||||
// ...
|
||||
import { IdempotencyKeyService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
idempotencyKeyService: IdempotencyKeyService
|
||||
}
|
||||
|
||||
class CartCompletionStrategy
|
||||
extends AbstractCartCompletionStrategy {
|
||||
|
||||
protected readonly idempotencyKeyService_:
|
||||
IdempotencyKeyService
|
||||
|
||||
constructor(
|
||||
{ idempotencyKeyService }: InjectedDependencies
|
||||
) {
|
||||
super(arguments[0])
|
||||
this.idempotencyKeyService_ = idempotencyKeyService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CartCompletionStrategy
|
||||
```
|
||||
|
||||
In the above example, you inject the `IdempotencyKeyService` in the constructor. This allows you to use the `IdempotencyKeyService` within your class.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement the complete Method
|
||||
|
||||
The cart completion strategy is required to implement a single method: the `complete` method. This method is used in the [Complete Cart endpoint](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) to handle the logic of completing the cart.
|
||||
|
||||
The method accepts three parameters:
|
||||
|
||||
- `cartId`: the first parameter of the method, which is a string indicating the ID of the cart to complete.
|
||||
- `idempotencyKey`: the second parameter of the method, which is an instance of the `IdempotencyKey` entity. The idempotency key is retrieved based on the idempotency key passed in the header of the request, and it’s used to determine the current point reached in the checkout flow to avoid inconsistencies on interruptions. You can learn more about the idempotency key [here](../cart.md#idempotency-key). You can also learn how to use it within your strategy by following [this guide](../../../development/idempotency-key/use-service.md)
|
||||
- `context`: the third parameter of the method, which is an object that holds a single property `ip`. `ip` is a string indicating the IP of the customer.
|
||||
|
||||
The completion strategy is expected to return an object with the following properties:
|
||||
|
||||
- `response_code`: a number indicating the response code.
|
||||
- `response_body`: an object that will be returned to the client.
|
||||
|
||||
You can refer to this guide to learn how the cart conceptual guide is implemented in the Medusa backend. This can help you understand how details such as inventory, taxes, and more are handled.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, try out your strategy using the Complete Cart endpoint. You should see the logic you implemented used for completing the cart.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to implement cart functionalities in the storefront](../storefront/implement-cart.mdx)
|
||||
- [How to implement checkout flow in the storefront](../storefront/implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
description: 'Learn about the Cart entity, its relation to other entities, and how the cart completion process is implemented.'
|
||||
---
|
||||
|
||||
# Cart Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the Cart entity, its relation to other entities, and how the cart completion strategy is implemented.
|
||||
|
||||
## Overview
|
||||
|
||||
The cart allows customers to go from browsing products to completing an order. Many actions can be performed on a cart, like adding line items, applying discounts, creating shipping methods, and more before eventually completing it and creating an [Order](../orders/orders.md).
|
||||
|
||||
---
|
||||
|
||||
## Cart Entity Overview
|
||||
|
||||
A cart is represented by the `Cart` entity. Some of the `Cart` entity’s attributes include:
|
||||
|
||||
- `email`: The email the cart is associated with.
|
||||
- `type`: A string indicating what the cart is used for. Its value can be:
|
||||
- `default` if the cart is used to place an order.
|
||||
- `swap` if the cart is used to create and register a swap
|
||||
- `draft_order` if the cart is used to create and complete a draft order.
|
||||
- `payment_link` if the cart is used for a payment link.
|
||||
- `claim` if the cart is used to create a claim.
|
||||
- `completed_at`: the date the cart was completed. A completed cart means that it has been used for its main purpose. For example, if the cart is used to place an order, then a completed cart means that the order was placed.
|
||||
- `payment_authorized_at`: the date a payment was authorized.
|
||||
|
||||
There are other important attributes discussed in later sections. Check out the [full Cart entity in the entities reference](../../references/entities/classes/Cart.md).
|
||||
|
||||
---
|
||||
|
||||
## Cart Totals Calculation
|
||||
|
||||
By default, the `Cart` entity doesn’t hold any details regarding the totals. These are computed and added to the cart instance using the `CartService`'s [decorateTotals method](../../references/services/classes/CartService.md#decoratetotals). There's also a dedicated method in the `CartService`, [retrieveWithTotals](../../references/services/classes/CartService.md#retrieveWithTotals), attaching the totals automatically. It is recommended to use this method by default when you need to retrieve the cart.
|
||||
|
||||
The cart’s totals are calculated based on the content and context of the cart. This includes the selected region, whether tax-inclusive pricing is enabled, the chosen shipping methods, and more.
|
||||
|
||||
The calculated cart’s totals include:
|
||||
|
||||
- `shipping_total`: The total of the chosen shipping methods, with taxes.
|
||||
- `shipping_tax_total`: The applied taxes on the shipping total.
|
||||
- `discount_total`: The total of the applied discounts.
|
||||
- `raw_discount_total`: The total of the applied discounts without rounding.
|
||||
- `item_tax_total`: The total applied taxes on the cart’s items.
|
||||
- `tax_total`: The total taxes applied (the sum of `shipping_tax_total` and `item_tax_total`).
|
||||
- `gift_card_total`: The total gift card amount applied on the cart. If there are any taxes applied on the gift cards, they’re deducted from the total.
|
||||
- `gift_card_tax_total`: The total taxes applied on the cart’s gift cards.
|
||||
- `subtotal`: The total of the items without taxes or discounts.
|
||||
- `total`: The overall total of the cart.
|
||||
|
||||
:::note
|
||||
|
||||
If you have tax-inclusive pricing enabled, you can learn about other available total fields [here](../taxes/inclusive-pricing.md#retrieving-tax-amounts).
|
||||
|
||||
:::
|
||||
|
||||
The cart’s totals are retrieved by default in all the [cart’s store APIs](https://docs.medusajs.com/api/store#carts).
|
||||
|
||||
---
|
||||
|
||||
## Cart Completion
|
||||
|
||||
The cart completion functionality is implemented inside the strategy `cartCompletionStrategy`. This allows you to customize how the process is implemented in your store.
|
||||
|
||||
You can initiate the cart completion process by sending a request to the [Complete Cart endpoint](https://docs.medusajs.com/api/store#carts_postcartscartcomplete).
|
||||
|
||||
### Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a cart. It is generated when the cart completion process is started and can be used to retry cart completion safely if an error occurs. The idempotency key is stored in the Cart entity under the attribute `idempotency_key`.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
### Cart Completion Process
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn how to override the cart completion strategy [here](./backend/cart-completion-strategy.md).
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
The process is implemented as follows:
|
||||
|
||||
1. When the idempotency key’s recovery point is set to `started`, the tax lines are created for the items in the cart. This is done using the `CartService`'s [createTaxLines method](../../references/services/classes/CartService.md#createtaxlines). If that is completed with no errors, the recovery point is set to `tax_lines_created` and the process continues.
|
||||
2. When the idempotency key’s recovery point is set to `tax_lines_created`, the payment is authorized using the `CartService`'s method [authorizePayment](../../references/services/classes/CartService.md#authorizepayment). If the payment requires more action or is pending authorization, then the tax lines that were created in the previous steps are deleted and the cart completion process is terminated. Once the payment is authorized, the process can be restarted.
|
||||
3. When the idempotency key’s recovery point is set to `payment_authorized`, tax lines are created again the same way as the first step. Then, the inventory of each of the items in the cart is confirmed using the `ProductVariantInventoryService`'s method [confirmInventory](../../references/services/classes/ProductVariantInventoryService.md#confirminventory). If an item is in stock, the quantity is reserved using the `ProductVariantInventoryService`'s method [reserveQuantity](../../references/services/classes/ProductVariantInventoryService.md#reservequantity). If an item is out of stock, any item reservations that were created are deleted, the payment is canceled, and an error is thrown, terminating the cart completion process. If all item quantities are confirmed to be available:
|
||||
1. If the cart belongs to a swap (the `type` attribute is set to `swap`), the swap is registered as completed using the `SwapService`'s [registerCartCompletion method](../../references/services/classes/SwapService.md#registercartcompletion) and the inventory item reservations are removed using the Inventory module. The process ends successfully here for a swap.
|
||||
2. If the cart belongs to an order, the order is created using the `OrderService`'s method [createFromCart](../../references/services/classes/OrderService.md#createfromcart). The order is then retrieved and sent in the response.
|
||||
4. Once the process detailed above is done, the idempotency key’s recovery point is set to `finished`.
|
||||
|
||||
---
|
||||
|
||||
## Cart’s Relation to Other Entities
|
||||
|
||||
### Region
|
||||
|
||||
A cart is associated with a [region](../regions-and-currencies/regions.md), which is represented by the `Region` entity. This ensures the prices, discounts, and other conditions that depend on the region are accurate for a customer.
|
||||
|
||||
The region’s ID is stored in the `Cart` entity under the attribute `region_id`. You can also access the region by expanding the `region` relation and accessing `cart.region`.
|
||||
|
||||
### Sales Channel
|
||||
|
||||
A [sales channel](../sales-channels/sales-channels.md) indicates different selling points a business offers its products in. It is represented by the `SalesChannel` entity.
|
||||
|
||||
A cart can be associated with a sales channel. When adding products to the cart, it’s important that the cart and the product belong to the same sales channel.
|
||||
|
||||
The sales channel’s ID is stored in the `sales_channel_id` attribute of the `Cart` entity. You can also access the sales channel by expanding the `sales_channel` relation and accessing `cart.sales_channel`.
|
||||
|
||||
### Address
|
||||
|
||||
A cart can have a shipping and a billing address, both represented by the `Address` entity.
|
||||
|
||||
The billing address’s ID is stored in the `billing_address_id` attribute of the `Cart` entity. You can also access the billing address by expanding the `billing_address` relation and accessing `cart.billing_address`.
|
||||
|
||||
The shipping address’s ID is stored in the `shipping_address_id` attribute of the `Cart` entity. You can also access the shipping address by expanding the `shipping_address` relation and accessing `cart.shipping_address`.
|
||||
|
||||
### LineItem
|
||||
|
||||
Products added to the cart are represented by the `LineItem` entity. You can access the cart’s items by expanding the `items` relation and accessing `cart.items`.
|
||||
|
||||
### Discount
|
||||
|
||||
[Discounts](../discounts/discounts.md) can be added to the cart to deduct the cart’s total. A discount is represented by the `Discount` entity.
|
||||
|
||||
You can access the discounts applied on a cart by expanding the `discounts` relation and accessing `cart.discounts`.
|
||||
|
||||
### GiftCard
|
||||
|
||||
[Gift cards](../gift-cards/gift-cards.md) can be applied on a cart to benefit from a pre-paid balance during payment. A gift card is represented by the `GiftCard` entity.
|
||||
|
||||
You can access the gift cards applied on a cart by expanding the `gift_cards` relation and accessing `cart.gift_cards`.
|
||||
|
||||
### Customer
|
||||
|
||||
A cart can be associated with either a logged-in [customer](../customers/customers.md) or a guest customer, both represented by the `Customer` entity.
|
||||
|
||||
You can access the customer by expanding the `customer` relation and accessing `cart.customer`.
|
||||
|
||||
### PaymentSession
|
||||
|
||||
A payment session is an available payment method that the customer can use during the checkout process. It is represented by the `PaymentSession` entity.
|
||||
|
||||
A cart can have multiple payment sessions that the customer can choose from. You can access the payment sessions by expanding the `payment_sessions` relation and accessing `cart.payment_sessions`.
|
||||
|
||||
You can also access the currently selected payment session through `cart.payment_session`.
|
||||
|
||||
### Payment
|
||||
|
||||
A payment is the authorized amount required to complete the cart. It is represented by the `Payment` entity.
|
||||
|
||||
The ID of the payment is stored in the `payment_id` attribute of the `Cart` entity. You can also access the payment by expanding the `payment` relation and accessing `cart.payment`.
|
||||
|
||||
### ShippingMethod
|
||||
|
||||
A shipping method indicates the chosen shipping method used to fulfill the order created later. It is represented by the `ShippingMethod` entity.
|
||||
|
||||
A cart can have more than one shipping method. You can access the shipping methods by expanding the `shipping_methods` relation and accessing `cart.shipping_methods`.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to override the cart completion strategy](./backend/cart-completion-strategy.md)
|
||||
- [How to implement the cart functionality in a storefront](./storefront/implement-cart.mdx)
|
||||
- [How to implement the checkout flow in a storefront](./storefront/implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
description: "A cart is a virtual shopping basket that customers can use to pick products they want to purchase. Learn about the available features and guides."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Carts and Checkout
|
||||
|
||||
A cart is a virtual shopping basket that customers can use to pick products they want to purchase. Checkout is the process of the customer placing an order. This overview introduces the available features related to carts and checkout.
|
||||
|
||||
## Features
|
||||
|
||||
### Cart Management
|
||||
|
||||
Customers can manage their cart including adding, updating, and removing items from the cart.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/storefront/implement-cart',
|
||||
label: 'Storefront: Implement Cart',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement cart functionality in a storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#carts',
|
||||
label: 'Storefront APIs: Carts',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Carts.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Shipping and Payment
|
||||
|
||||
Developers can integrate any third-party provider or custom logic to offer shipping and payment options for customers during checkout. They can integrate them using existing plugins or by creating their own.
|
||||
|
||||
Admins can specify available shipping and payment processors during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx).
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-fulfillment-provider',
|
||||
label: 'Backend: Create Fulfillment Provider',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a fulfillment provider in the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Backend: Create Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a payment processor in the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/regions/providers',
|
||||
label: 'User Guide: Manage Providers',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage available providers using Medusa Admin.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Checkout Flow
|
||||
|
||||
Developers can implement a seamless checkout flow that include steps related to shipping and payment, tax calculation, and more.
|
||||
|
||||
Customers can place orders using the checkout flow.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/cart-completion-strategy',
|
||||
label: 'Backend: Override Cart Completion',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to override the cart completion strategy.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/storefront/implement-checkout-flow',
|
||||
label: 'Storefront: Implement Checkout',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to implement the checkout flow in a storefront.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store#carts',
|
||||
label: 'Storefront APIs: Cart',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check available Store REST APIs for Carts related to checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understand the Architecture
|
||||
|
||||
Learn how cart-related entities are build, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/cart',
|
||||
label: 'Architecture: Cart',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the cart completion process.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/shipping',
|
||||
label: 'Architecture: Shipping',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Shipping architecture.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/payment',
|
||||
label: 'Architecture: Payment',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Payment architecture.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Carts and Checkout’s relation to other modules in Medusa
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Products',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Customers can add products to cart and purchase them.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/taxes/overview',
|
||||
label: 'Taxes',
|
||||
customProps: {
|
||||
icon: Icons['cash-solid'],
|
||||
description: 'Taxes can be calculated for a cart either automatically or manually.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/discounts/overview',
|
||||
label: 'Discounts',
|
||||
customProps: {
|
||||
icon: Icons['currency-dollar-solid'],
|
||||
description: 'Discounts can be applied on a cart to reduce its total.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/gift-cards/overview',
|
||||
label: 'Gift Cards',
|
||||
customProps: {
|
||||
icon: Icons['gift-solid'],
|
||||
description: 'Purchased gift cards can be applied during checkout.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
description: 'Learn about the payment architecture in the Medusa backend. The payment architecture refers to all operations in the ecommerce store related to processing payments.'
|
||||
---
|
||||
|
||||
# Payment Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the payment architecture in Medusa, specifically its 3 main components and the idempotency key.
|
||||
|
||||
## Introduction
|
||||
|
||||
The payment architecture refers to all operations in a commerce application related to processing a customer’s payment. It includes the checkout flow and order handling including refunds and swaps.
|
||||
|
||||
In Medusa, there are 3 main components in the payment architecture: Payment Processor, Payment Session, and Payment.
|
||||
|
||||
:::note
|
||||
|
||||
Payment Processors were previously named Payment Provider. With the roll-out of the Payment Processor API following v1.8 of the core Medusa package, Payment Provider is considered a legacy now. However, there are certain entities and services that still use the name Payment Provider as they still don't follow this change.
|
||||
|
||||
:::
|
||||
|
||||
1. A **Payment Processor** is a service or method used to capture, authorize, and refund payments, among other functionalities.
|
||||
2. A **Payment Session** is a session associated with a cart and created during a customer’s checkout flow. It is controlled by the **Payment Processor** to authorize the payment and is used eventually to create a **Payment**.
|
||||
3. A **Payment** is associated with an order and it represents the amount authorized for the purchase. It is used later for further payment operations such as capturing or refunding payments.
|
||||
|
||||
An important part in the Payment architecture to understand is the **Idempotency Key**. It’s a unique value that’s generated for a cart and is used to retry payments during checkout if they fail.
|
||||
|
||||
---
|
||||
|
||||
## Payment Processor
|
||||
|
||||
A Payment Processor in Medusa is a method to handle payments in selected regions. It is not associated with a cart, customer, or order in particular. It provides the necessary implementation to create Payment Sessions and Payments, as well as authorize and capture payments, among other functionalities.
|
||||
|
||||
Payment Processors can be integrated with third-party services that handle payment operations such as capturing a payment. An example of a Payment Processor is Stripe.
|
||||
|
||||
Payment Processors can also be related to a custom way of handling payment operations. An example of that is Cash on Delivery (COD) payment methods or Medusa’s [manual payment provider plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) which provides a minimal implementation of a payment provider and allows store operators to manually handle order payments.
|
||||
|
||||
:::tip
|
||||
|
||||
The manual payment plugin is still considered a payment provider since it does not follow the Payment Processor API introduced in v1.8 of the core Medusa package.
|
||||
|
||||
:::
|
||||
|
||||
### How Payment Processor is Created
|
||||
|
||||
A Payment Processor is essentially a Medusa [service](../../development/services/create-service.mdx) with a unique identifier, and it extends the `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`. You can create it as part of a [plugin](../../development/plugins/overview.mdx), or just as a service file in your Medusa backend.
|
||||
|
||||
As a developer, you will mainly work with the Payment Processor when integrating a payment method in Medusa.
|
||||
|
||||
When you run your Medusa backend, the Payment Processor will be registered on your backend if it hasn’t been already.
|
||||
|
||||
Once the Payment Processor is added to the backend, the store operator will be able to choose using the [admin dashboard](../../admin/quickstart.mdx) the payment processors available in a region. You can alternatively do that using the [admin APIs](https://docs.medusajs.com/api/admin). These payment processors are shown to the customer at checkout as payment methods to choose from and use.
|
||||
|
||||
:::caution
|
||||
|
||||
It’s important to enable a payment processor in a region, or else the payment processor cannot be used by customers on checkout.
|
||||
|
||||
:::
|
||||
|
||||
### PaymentProvider Entity Overview
|
||||
|
||||
The [`PaymentProvider`](../../references/entities/classes/PaymentProvider.md) entity only has 2 attributes: `is_installed` which is a boolean value indicating whether the Payment Processor is installed; and `id` which is the unique identifier that you define in the Payment Processor service.
|
||||
|
||||
---
|
||||
|
||||
## Payment Session
|
||||
|
||||
Payment Sessions are linked to a customer’s cart. Each Payment Session is associated with a payment processor that is available in the customer cart’s region.
|
||||
|
||||
They hold the status of the payment flow throughout the checkout process which can be used to indicate different statuses such as an authorized payment or payment that requires more actions from the customer.
|
||||
|
||||
After the checkout process is completed and the Payment Session has been authorized successfully, a Payment instance will be created to be associated with the customer’s order and will be used for further actions related to that order.
|
||||
|
||||
### How Payment Session is Created
|
||||
|
||||
After the customer adds products to the cart, proceeds with the checkout flow, and reaches the payment method section, Payment Sessions are created for each Payment Processor available in that region.
|
||||
|
||||
During the creation of the Payment Session, the Payment Processor can interact with third-party services for any initialization necessary on their side. For example, when a Payment Session for Stripe is being created, a payment intent associated with the customer is created with Stripe as well.
|
||||
|
||||
Payment Sessions can hold data that is necessary for the customer to complete their payment.
|
||||
|
||||
Among the Payment Sessions available only one will be selected based on the customer’s payment processor of choice. For example, if the customer sees that they can pay with Stripe or PayPal and chooses Stripe, Stripe’s Payment Session will be the selected Payment Session of that cart.
|
||||
|
||||
### PaymentSession Entity Overview
|
||||
|
||||
The [`PaymentSession`](../../references/entities/classes/PaymentSession.md) entity belongs to a `Cart`. This is the customer‘s cart that was used for checkout which lead to the creation of the Payment Session.
|
||||
|
||||
The `PaymentSession` instance also belongs to a `PaymentProvider` instance. This is the Payment Processor that was used to create the Payment Session and that controls it for further actions like authorizing the payment.
|
||||
|
||||
The `data` attribute is an object that holds any data required for the Payment Processor to perform payment operations like authorizing or capturing payment. For example, when a Stripe payment session is initialized, the `data` object will hold the payment intent among other data necessary to authorize the payment.
|
||||
|
||||
The `is_selected` attribute in the `PaymentSession` entity is a boolean value that indicates whether this Payment Session was selected by the customer to pay for their purchase. Going back to the previous example of having Stripe and PayPal as the available Payment Processors, when the customer chooses Stripe, Stripe’s Payment Session will have `is_selected` set to true whereas PayPal’s Payment Session will have `is_selected` set to false.
|
||||
|
||||
The `status` attributes indicates the current status of the Payment Session. It can be one of the following values:
|
||||
|
||||
- `authorized`: The payment has been authorized which means the order can be placed successfully.
|
||||
- `pending`: The payment is still pending further actions. This is usually used when the payment session is initialized.
|
||||
- `requires_more`: The payment requires additional actions from the customer before the payment can be authorized successfully and the order can be placed. An example of this is payment methods that require 3-D Secure checks.
|
||||
- `error`: An error was encountered when an authorization was attempted. This status is usually used when an error has been encountered when authorizing the payment with a third-party payment processor.
|
||||
- `canceled`: The payment has been canceled.
|
||||
|
||||
These statuses are important in the checkout flow to determine the current step the customer is at and which action should come next. For example, if there is an attempt to place the order but the status of the Payment Session is not `authorized`, an error will be thrown.
|
||||
|
||||
---
|
||||
|
||||
## Payment
|
||||
|
||||
A Payment is used to represent the amount authorized for a customer’s purchase. It is associated with the order placed by the customer and will be used after that for all operations related to the order’s payment such as capturing or refunding the payment.
|
||||
|
||||
Payments are generally created using data from the Payment Session and it holds any data that can be necessary to perform later payment operations.
|
||||
|
||||
### How Payment is Created
|
||||
|
||||
Once the customer completes their purchase and the payment has been authorized, a Payment instance will be created from the Payment Session. The Payment is associated first with the cart and then with the order once it’s created and placed.
|
||||
|
||||
When the store operator then chooses to capture the order from the Medusa Admin, the Payment is used by the Payment Processor to capture the payment. This is the same case for refunding the amount, canceling the order, or creating a swap.
|
||||
|
||||
### Payment Entity Overview
|
||||
|
||||
The [`Payment`](../../references/entities/classes/Payment.md) entity belongs to the `Cart` that it was originally created from when the customer’s payment was authorized. It also belongs to an `Order` once it’s placed. Additionally, it belongs to a `PaymentProvider` which is the payment processor that the customer chose on checkout.
|
||||
|
||||
In case a `Swap` is created for an order, `Payment` will be associated with that swap to handle payment operations related to it.
|
||||
|
||||
Similar to `PaymentSession`, `Payment` has a `data` attribute which is an object that holds any data required to perform further actions with the payment such as capturing the payment.
|
||||
|
||||
`Payment` also holds attributes like `amount` which is the amount authorized for payment, and `amount_refunded` which is the amount refunded from the original amount if a refund has been initiated.
|
||||
|
||||
Additionally, `Payment` has the `captured_at` date-time attribute which is filled when the payment has been captured, and a `canceled_at` date-time attribute which is filled when the order has been canceled.
|
||||
|
||||
---
|
||||
|
||||
## Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a cart. It is generated at the last step of checkout before authorization of the payment is attempted and used in the request and response header.
|
||||
|
||||
If the request is interrupted for any reason or the payment fails, the client can retry completing the check out using the Idempotency Key, and the flow will continue from the last stored step. This prevents any payment issues from occurring with the customers and allows for secure retries of failed payments or interrupted connections.
|
||||
|
||||
You can learn more about idempotency keys [here](../../development/idempotency-key/overview.mdx).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Available Payment Plugins](../../plugins/payment/index.mdx)
|
||||
- [Create a Payment Processor](./backend/add-payment-provider.md)
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
description: 'Learn how the shipping architecture is implemented in the Medusa backend. This includes an overview of what the Fulfillment Provider, Shipping Profile, Shipping Option, and Shipping Methods.'
|
||||
---
|
||||
|
||||
# Shipping Architecture Overview
|
||||
|
||||
This document gives an overview of the shipping architecture and its four most important components.
|
||||
|
||||
## Introduction
|
||||
|
||||
In Medusa, the Shipping architecture relies on 4 components: **Fulfillment Provider**, **Shipping Profiles**, **Shipping Options**, and **Shipping Methods**.
|
||||
|
||||
The distinction between the four is important. It has been carefully planned and put together to support all the different ecommerce use cases and shipping providers that can be integrated.
|
||||
|
||||
It’s also constructed to support multiple regions, provide different shipment configurations and options for different product types, provide promotional shipments for your customers, and much more.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- **Fulfillment Provider:** Fulfillment providers are plugins or [Services](../../development/services/create-service.mdx) used to ship the products to your customers, whether physically or virtually. An example of a fulfillment provider would be FedEx.
|
||||
- **Shipping Profiles:** created by the admin. They are used to group products that should be shipped in a different manner than the default. Shipping profiles can have multiple shipping options.
|
||||
- **Shipping Options:** created by the admin and belong to a shipping profile. They are specific to certain regions and can have cart conditions. They use an underlying fulfillment provider. Once a customer checks out, they can choose the shipping option that’s available and most relevant to them.
|
||||
- **Shipping Method:** created when the customer chooses a shipping option on checkout. The shipping method is basically a copy of the shipping option, but with values specific to the customer and the cart it’s associated with. When the order is placed, the shipping method will then be associated with the order and fulfilled based on the integration with the fulfillment provider.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Provider
|
||||
|
||||
A Fulfillment Provider in Medusa is a method to handle shipping products in selected regions. It is not associated with a cart, customer, or order in particular.
|
||||
|
||||
It provides the necessary implementation to create Fulfillments for orders and ship items to customers. They can also be used for order returns and swaps.
|
||||
|
||||
Fulfillment Providers can be integrated with third-party services that handle the actual shipment of products. An example of a Fulfillment Provider is FedEx.
|
||||
|
||||
Fulfillment Providers can also be related to a custom way of handling fulfillment operations. An example of that is Medusa’s [manual fulfillment provider plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-fulfillment-manual) which provides a minimal implementation of a fulfillment provider and allows store operators to manually handle order fulfillments.
|
||||
|
||||
### How Fulfillment Provider is Created
|
||||
|
||||
A Fulfillment Provider is essentially a Medusa [Service](../../development/services/create-service.mdx) with a unique identifier, and it extends the `FulfillmentService` provided by the `medusa-interfaces` package. It can be created as part of a [plugin](../../development/plugins/overview.mdx), or it can be created just as a Service file in your Medusa backend.
|
||||
|
||||
As a developer, you will mainly work with the Fulfillment Provider when integrating a fulfillment method in Medusa.
|
||||
|
||||
When you run your Medusa backend, the Fulfillment Provider will be registered on your backend if it hasn’t been already.
|
||||
|
||||
Once the Fulfillment Provider is added to the backend, the store operator will be able to associate on the [Medusa Admin](../../development/backend/install.mdx) the Fulfillment Provider with shipping options.
|
||||
|
||||
### FulfillmentProvider Entity Overview
|
||||
|
||||
The [`FulfillmentProvider`](../../references/entities/classes/FulfillmentProvider.md) entity only has 2 attributes: `is_installed` to indicate if the fulfillment provider is installed and its value is a boolean; and `id` which is the unique identifier that you define in the Fulfillment Provider Service.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Profile
|
||||
|
||||
Shipping profiles are the highest in the hierarchy in the shipping architecture.
|
||||
|
||||
Shipping profiles are created by the admin. The admin can specify the name of the shipping profile which will be a name that the customer can see.
|
||||
|
||||
A shipping profile is not associated with any fulfillment providers. It has multiple shipping options that can be associated with different providers.
|
||||
|
||||
### Purpose of Shipping Profile
|
||||
|
||||
Shipping profiles are used to group products that can be shipped in the same manner.
|
||||
|
||||
The default shipping profile is one that groups all of your store’s products. You also get a shipping profile that’s specific to gift cards. This is because, generally speaking, all products would be delivered similarly, whereas gift cards would be delivered in different behavior.
|
||||
|
||||
Although this might be the general case, there are still some use cases where you will have a set of products that should be shipped differently than others.
|
||||
|
||||
For example, shipping heavy items might be more expensive than others, which would enforce different price rates. In that case, you can create a new shipping profile that groups together heavy products. This would allow you to give these products more suitable price rates when creating their shipping options.
|
||||
|
||||
### ShippingProfile Entity Overview
|
||||
|
||||
The [`ShippingProfile`](../../references/entities/classes/ShippingProfile.md) entity can have a set of `Product` instances. These would be the products the shipping profile is providing shipping options for.
|
||||
|
||||
The `ShippingProfile` has a `type` attribute that can be `default`, `gift_card`, or `custom`.
|
||||
|
||||
The `ShippingProfile` entity also has an array of `ShippingOption` instances.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Option
|
||||
|
||||
After the admin adds a shipping profile, they can add shipping options that belong to that shipping profile from the admin dashboard.
|
||||
|
||||
Shipping options have a set of conditions like the region they’re available in or cart-specific conditions. For example, if your company operates in the United States as well as Germany, you might use a different shipping option for each of the two countries.
|
||||
|
||||
Among the configurations that the admin has to set when creating a shipping option is specifying the fulfillment provider it uses. This means that when you create a plugin for a fulfillment provider, that provider needs to be chosen as the fulfillment provider of a shipping option to be used in the store.
|
||||
|
||||
Shipping options are only shown to a customer during checkout if their cart satisfies the option’s conditions. Also, as they belong to a shipping profile, they’re only shown when products that belong to the same shipping profile are in the cart.
|
||||
|
||||
### Purpose of Shipping Option
|
||||
|
||||
The first purpose that a shipping option has is showing the customer during checkout what shipping options are available for them.
|
||||
|
||||
Then, once the customer chooses a shipping option, that shipping option is used to create a shipping method with details specific to the customer and their cart. Then, the shipping method is associated with the cart, and the shipping option remains untouched.
|
||||
|
||||
Think of a shipping option as a template defined by the admin that indicates what data and values the shipping method should have when it’s chosen by the customer during checkout.
|
||||
|
||||
### ShippingOption Entity Overview
|
||||
|
||||
The [`ShippingOption`](../../references/entities/classes/ShippingOption.md) entity belongs to the `ShippingProfile` entity.
|
||||
|
||||
The `ShippingOption` entity also belongs to a `FulfillmentProvider`. This can be either a custom third-party provider or one of Medusa’s default fulfillment providers.
|
||||
|
||||
It has the `price_type` attribute to indicate whether the shipping option’s rate is `calculated` by the provider or a fixed `flat_rate` price. It also has the `amount` attribute to set an amount for the shipping option if the `price_type` is `flat_rate`.
|
||||
|
||||
`ShippingOption` also belongs to a `Region`, which resembles one or more countries. This defines where the shipping option is available.
|
||||
|
||||
`ShippingOption` has a set of `ShippingOptionRequirement` instances. The `ShippingOptionRequirement` entity allows defining cart rules which determine whether the shipping option will be available or not for a customer during checkout. For example, you can set a minimum subtotal amount for a shipping option to be available for a customer’s cart.
|
||||
|
||||
The `is_return` attribute is used to indicate whether the shipping option is used for shipping orders or returning orders. Shipping options can only be used for one or the other.
|
||||
|
||||
The `data` attribute is used to specify any data necessary for fulfilling the shipment based on the underlying fulfillment provider. When you integrate a fulfillment provider, you can check in that provider’s documentation for any data necessary when creating a new shipment.
|
||||
|
||||
The `data` attribute does not have any specific format. It’s up to you to choose whatever data is included here.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Method
|
||||
|
||||
Unlike the previous two components, a shipping method is not created by the admin. It’s created when a `POST` request is sent to `/store/carts/:id/shipping-methods` after the customer chooses a shipping option.
|
||||
|
||||
The shipping method will be created based on the chosen shipping option and it’ll be associated with the customer’s cart. Then, when the order is placed, the shipping method is associated with the order.
|
||||
|
||||
A shipping method can be fulfilled automatically or manually through the admin dashboard. This is based on the fulfillment provider associated with the shipping option the shipping method is based on.
|
||||
|
||||
### Shipping Method Purpose
|
||||
|
||||
It’s important to understand the distinction between shipping methods and shipping options. Shipping options are templates created by the admin to indicate what shipping options should be shown to a customer. This provides customization capabilities in a store, as an admin is free to specify configurations for that option such as what fulfillment provider it uses or what are its rates.
|
||||
|
||||
When handling the order and fulfilling it, you, as a developer, will be mostly interacting with the shipping method.
|
||||
|
||||
This separation allows for developers to implement the custom integration with third-party fulfillment providers as necessary while also ensuring that the admin has full control of their store.
|
||||
|
||||
### ShippingMethod Entity Overview
|
||||
|
||||
A lot of the shipping method’s attributes are similar to the shipping option’s attribute.
|
||||
|
||||
The [`ShippingMethod`](../../references/entities/classes/ShippingMethod.md) entity belongs to a `ShippingOption`.
|
||||
|
||||
Similar to the `data` attribute explained for the `ShippingOption` entity, a `ShippingMethod` has a similar `data` attribute that includes all the data to be sent to the fulfillment provider when fulfilling the order.
|
||||
|
||||
The `ShippingMethod` belongs to a `Cart`. This is the cart the customer is checking out with.
|
||||
|
||||
The `ShippingMethod` also belongs to the `Order` entity. This association is accomplished when the order is placed.
|
||||
|
||||
The `ShippingMethod` instance holds a `price` attribute, which will either be the flat rate price or the calculated price.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Fulfillment Provider](./backend/add-fulfillment-provider.md)
|
||||
- [Available shipping plugins](https://github.com/medusajs/medusa/tree/master/packages)
|
||||
@@ -0,0 +1,654 @@
|
||||
---
|
||||
description: 'Learn how to implement the cart functionality in your storefront using the REST APIs. This includes creating a cart, updating a cart, adding products to the cart, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Add Cart Functionality
|
||||
|
||||
This document guides you through how you can add cart-related functionalities to your storefront. That includes creating and updating a cart and managing items in the cart.
|
||||
|
||||
## Overview
|
||||
|
||||
Carts are necessary for ecommerce platforms to allow customers to buy products. Each customer, whether logged in or as a guest, should have a cart associated with them. The customer can then add products to the cart.
|
||||
|
||||
This document helps you understand how to add the cart functionality to your storefront. This is helpful if you’re creating the storefront from scratch, or you want to understand how the process generally works in Medusa’s starter storefronts.
|
||||
|
||||
:::note
|
||||
|
||||
This document does not cover implementing the checkout flow. You can refer to [this documentation instead to learn how to implement the checkout flow](./implement-checkout-flow.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) and have [created an instance of the client](../../../js-client/overview.md#configuration).
|
||||
|
||||
### Medusa React
|
||||
|
||||
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||
|
||||
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
---
|
||||
|
||||
## Create a Cart
|
||||
|
||||
You can create a cart with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.create()
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{}, // create an empty cart
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request does not require any parameters. It returns the created cart in the response.
|
||||
|
||||
The cart by default will have a random region assigned to it. You can specify the cart's region by passing in the request's body a `region_id` parameter:
|
||||
|
||||
Otherwise, you can assign it a specific region during creation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.create({
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{
|
||||
region_id,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Check out the [API Reference](https://docs.medusajs.com/api/store#carts_postcart) for a full list of available request body parameters.
|
||||
|
||||
:::note
|
||||
|
||||
The region a cart is associated with determines the currency the cart uses, the tax, payment, and fulfillment providers, and other details and options. So, make sure you use the correct region for a cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Cart
|
||||
|
||||
Notice that in the previous code snippets, you set the cart’s ID in the local storage. This is helpful to persist the customer’s cart even when they leave the website and come back later.
|
||||
|
||||
You can retrieve the cart at any given point using its ID with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
medusa.carts.retrieve(id)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React" default>
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart } = useCart()
|
||||
|
||||
return (
|
||||
<div>
|
||||
Items in Cart: {cart?.items.length || 0}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
fetch(`<BACKEND_URLL>/store/carts/${id}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter and returns the cart of that ID.
|
||||
|
||||
You can run this code snippet every time the storefront is opened. If a customer has a cart ID stored in their local storage, it’s loaded from the backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Make sure to remove the ID from the local storage after the customer places an order with this cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update a Cart
|
||||
|
||||
A cart has different data associated with it including the region, email, address, customer, and more.
|
||||
|
||||
You can use the following snippet to update any of the cart’s data:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeRegionId = (region_id: string) => {
|
||||
updateCart.mutate({
|
||||
region_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In its body, you can pass any data you want to update in the cart such as the region.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
Check out the full list of available request body parameters in the [API Reference](https://docs.medusajs.com/api/store#carts_postcart).
|
||||
|
||||
### Associate a Logged-In Customer with the Cart
|
||||
|
||||
A customer might add items to their cart, then creates an account or log in. In that case, you should ensure that the cart is associated with the logged-in customer moving forward.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
customer_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeCustomerId = (customer_id: string) => {
|
||||
updateCart.mutate({
|
||||
customer_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This updates the `customer_id` associated with the cart to make sure it belongs to a specific customer.
|
||||
|
||||
### Associate Guest Customers with a Cart using Email
|
||||
|
||||
In case the customer doesn't want to use their own account, you must at least associate an email address with the cart before completing the cart and placing the order.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeEmail = (email: string) => {
|
||||
updateCart.mutate({
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Line Item to the Cart
|
||||
|
||||
To create a line item of a product and add it to a cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.lineItems.create(cartId, {
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const createLineItem = useCreateLineItem(cart_id)
|
||||
|
||||
const handleAddItem = () => {
|
||||
createLineItem.mutate({
|
||||
variant_id,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In the body, it's required to send the ID of the product variant you want to add to the cart and its quantity.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
This adds a new line item to the cart. Line items can be accessed using `cart.items` which is an array that holds all line items in the cart. You can learn more about what properties line items have in the [API reference](https://docs.medusajs.com/api/store#carts_postcartscartlineitems).
|
||||
|
||||
:::note
|
||||
|
||||
If you’re using Sales Channels, make sure that the cart and the product belong to the same sales channel. You can update the cart’s sales channel by [updating the cart](#update-a-cart).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update Line Item in the Cart
|
||||
|
||||
To update a line item's quantity in the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.update(cartId, lineItemId, {
|
||||
quantity: 3,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const updateLineItem = useUpdateLineItem(cart_id)
|
||||
|
||||
const handleUpdateItem = () => {
|
||||
updateLineItem.mutate({
|
||||
lineId,
|
||||
quantity: 3,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 3,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters. In the body, it accepts the quantity of the line item.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Line Item from the Cart
|
||||
|
||||
To delete a line item from the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.delete(cartId, lineItemId)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useDeleteLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const deleteLineItem = useDeleteLineItem(cart_id)
|
||||
|
||||
const handleDeleteItem = () => {
|
||||
deleteLineItem.mutate({
|
||||
lineId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement the checkout flow in your storefront](./implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,597 @@
|
||||
---
|
||||
description: 'Learn how to implement the checkout flow in your storefront using the REST APIs. This includes adding steps related to shipping and payment, then placing the order.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Implement Checkout Flow
|
||||
|
||||
This document will guide you through the steps needed to implement the checkout flow in a Medusa storefront, including steps related to adding a custom payment processor.
|
||||
|
||||
## Overview
|
||||
|
||||
A checkout flow is composed of the necessary steps to allow a customer to perform a successful checkout. It’s generally made up of two primary steps: the shipping and payment steps.
|
||||
|
||||
This document will take you through the general process of a checkout flow. You should follow along with this document if you’re creating a custom storefront, if you’re adding a custom payment processor, or if you’re just interested in learning more about how checkout works in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
It’s recommended to go through the [Shipping Architecture Overview](../shipping.md) and [Payment Architecture Overview](../payment.md) first to have a better understanding of Medusa’s architecture.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js Starter Template](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) and have [created an instance of the client](../../../js-client/overview.md#configuration).
|
||||
|
||||
### Medusa React
|
||||
|
||||
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||
|
||||
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.mdx#cartprovider).
|
||||
|
||||
### Previous Steps
|
||||
|
||||
This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](./implement-cart.mdx#create-a-cart) and [associated with a logged-in or guest customer](./implement-cart.mdx#associate-a-logged-in-customer-with-the-cart). The cart should also have at least [one product in it](https://docs.medusajs.com/api/store#carts_postcartscartlineitems).
|
||||
|
||||
You can learn how to implement the cart flow using [this documentation](./implement-cart.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Shipping Step
|
||||
|
||||
In this step, the customer generally enters their shipping info, then chooses the available shipping option based on the entered info.
|
||||
|
||||
### Add Shipping Address
|
||||
|
||||
After the customer enters their shipping address information, you must send a `POST` request to the [Update a Cart](https://docs.medusajs.com/api/store#carts_postcartscart) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
shipping_address: {
|
||||
company,
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
address_2,
|
||||
city,
|
||||
country_code,
|
||||
province,
|
||||
postal_code,
|
||||
phone,
|
||||
},
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_address)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const addShippingAddress =
|
||||
(address: Record<string, string>) => {
|
||||
updateCart.mutate({
|
||||
shipping_address: {
|
||||
company: address.company,
|
||||
first_name: address.first_name,
|
||||
last_name: address.last_name,
|
||||
address_1: address.address_1,
|
||||
address_2: address.address_2,
|
||||
city: address.city,
|
||||
country_code: address.country_code,
|
||||
province: address.province,
|
||||
postal_code: address.postal_code,
|
||||
phone: address.phone,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
shipping_address: {
|
||||
company,
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
address_2,
|
||||
city,
|
||||
country_code,
|
||||
province,
|
||||
postal_code,
|
||||
phone,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_address)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter and the new shipping address in the request body.
|
||||
|
||||
The request returns the updated cart, with the new shipping address available in `cart.shipping_address`.
|
||||
|
||||
---
|
||||
|
||||
### List Shipping Options
|
||||
|
||||
After updating the cart with the customer’s address, the list of available [shipping options](../shipping.md#shipping-option) for that cart might change. So, you should retrieve the updated list of options.
|
||||
|
||||
You can retrieve the list of shipping options by sending a `GET` request to the [Retrieve Shipping Options for Cart API](https://docs.medusajs.com/api/store#shipping-options_getshippingoptionscartid) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.shippingOptions.listCartOptions(cartId)
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCartShippingOptions } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
cartId: string
|
||||
}
|
||||
|
||||
const ShippingOptions = ({ cartId }: Props) => {
|
||||
const { shipping_options, isLoading } =
|
||||
useCartShippingOptions(cartId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{shipping_options && !shipping_options.length && (
|
||||
<span>No shipping options</span>
|
||||
)}
|
||||
{shipping_options && (
|
||||
<ul>
|
||||
{shipping_options.map(
|
||||
(shipping_option) => (
|
||||
<li key={shipping_option.id}>
|
||||
{shipping_option.name}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShippingOptions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/shipping-options/${cartId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ shipping_options }) => {
|
||||
console.log(shipping_options.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter. It returns the array of [shipping options](https://docs.medusajs.com/api/store#shipping-options_getshippingoptions). Typically you would display those options to the customer to choose from.
|
||||
|
||||
### Choose Shipping Option
|
||||
|
||||
Once the customer chooses one of the available shipping options, send a `POST` request to the [Add a Shipping Method](https://docs.medusajs.com/api/store#carts_postcartscartshippingmethod) API endpoint. This will create a [shipping method](../shipping.md#shipping-method) based on the shipping option chosen and will associate it with the customer’s cart:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.addShippingMethod(cartId, {
|
||||
option_id: shippingOptionId, // the ID of the selected option
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_methods)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAddShippingMethodToCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const ShippingOptions = ({ cartId }: Props) => {
|
||||
// ...
|
||||
const addShippingMethod = useAddShippingMethodToCart(cartId)
|
||||
|
||||
const handleAddShippingMethod = (option_id: string) => {
|
||||
addShippingMethod.mutate({
|
||||
option_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ShippingOptions
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/shipping-methods`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
option_id: shippingOptionId, // ID of the selected option
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.shipping_methods)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter and its body the ID of the selected shipping option.
|
||||
|
||||
It returns the updated cart, with the created shipping method available in the array `cart.shipping_methods`.
|
||||
|
||||
---
|
||||
|
||||
## Payment Step
|
||||
|
||||
In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment processors is done differently for each processor, so this section will just show the general steps you should follow when implementing this step.
|
||||
|
||||
### Display Payment Methods
|
||||
|
||||
When the page opens and before the payment providers are displayed to the customer to choose from, you must create the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with.
|
||||
|
||||
To initialize the payment sessions, send a `POST` request to the [Initialize Payment Sessions](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.createPaymentSessions(cartId)
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart, startCheckout } = useCart()
|
||||
|
||||
useEffect(() => {
|
||||
startCheckout.mutate()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!cart?.payment_sessions.length && (
|
||||
<span>No payment processors</span>
|
||||
)}
|
||||
<ul>
|
||||
{cart?.payment_sessions.map(
|
||||
(paymentSession) => (
|
||||
<li key={paymentSession.id}>
|
||||
{paymentSession.provider_id}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-sessions`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_sessions)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint accepts the ID of the cart as a path parameter. It returns the updated cart with the initialized payment sessions available on `cart.payment_sessions`.
|
||||
|
||||
### Select Payment Session
|
||||
|
||||
When the customer chooses the payment processor they want to complete purchase with, you should select the payment session associated with that payment processor. To do that, send a `POST` request to the [Select a Payment Session](https://docs.medusajs.comapi/store#carts_postcartscartpaymentsession) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.setPaymentSession(cartId, {
|
||||
// retrieved from the payment session selected by the customer
|
||||
provider_id: paymentProviderId,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart, startCheckout, pay } = useCart()
|
||||
// ...
|
||||
|
||||
const handleSetPaymentSession = (provider_id: string) => {
|
||||
pay.mutate({
|
||||
provider_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-session`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
// the payment session selected by the customer
|
||||
provider_id: paymentProviderId,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter, and the ID of the payment processor in the request's body.
|
||||
|
||||
It returns the updated cart, with the selected payment session available under `cart.payment_session`.
|
||||
|
||||
:::tip
|
||||
|
||||
If you have one payment processor or if only one payment processor is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not.
|
||||
|
||||
:::
|
||||
|
||||
### Update Payment Session
|
||||
|
||||
This step is optional and is only necessary for some payment processors. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Processor to perform payment operations such as capturing payment.
|
||||
|
||||
If you need to update that data at any point before the purchase is made, send a request to [Update a Payment Session](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessionupdate) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
|
||||
data: {
|
||||
// pass any data you want to add in the `data` attribute
|
||||
// for example:
|
||||
"test": true,
|
||||
},
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session.data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdatePaymentSession, useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const PaymentProviders = () => {
|
||||
const { cart } = useCart()
|
||||
const updatePaymentSession = useUpdatePaymentSession(cart.id)
|
||||
// ...
|
||||
|
||||
const handleUpdatePaymentSession = (
|
||||
provider_id: string,
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
updatePaymentSession.mutate({
|
||||
provider_id,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PaymentProviders
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/store/carts/${cartId}/payment-sessions/${paymentProviderId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
// pass any data you want to add in the `data` attribute
|
||||
// for example:
|
||||
"test": true,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
console.log(cart.payment_session.data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the payment session's payment processor as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment processor.
|
||||
|
||||
It returns the updated cart. You can access the payment session's data on `cart.payment_session.data`.
|
||||
|
||||
### Complete Cart
|
||||
|
||||
The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment processor first to make sure the authorization is successful when you send the request to complete the cart.
|
||||
|
||||
To complete a cart, send a `POST` request to the [Complete a Cart](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.complete(cartId)
|
||||
.then(({ type, data }) => {
|
||||
console.log(type, data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
const Cart = () => {
|
||||
const { completeCheckout } = useCart()
|
||||
// ...
|
||||
|
||||
const handleCompleteCheckout = () => {
|
||||
completeCheckout.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/carts/${cartId}/complete`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ type, data }) => {
|
||||
console.log(type, data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter.
|
||||
|
||||
The request returns two properties: `type` and `data`. If the order was placed successfully, `type` will be `order` and `data` will be the order's data.
|
||||
|
||||
If an error occurred while placing the order, `type` will be `cart` and `data` will be the cart's data.
|
||||
Reference in New Issue
Block a user