# Reserve stock on demand and freeze prices Learn how to prevent overselling and improve the checkout experience by reserving items on demand while freezing all prices. In this tutorial, you will learn how to combine reserving stock with freezing Cart prices. This approach ensures that customers can complete their checkout with the products they want, at the prices they expect. You will learn to: - Enable reservation functionality in your Project. - Create a Cart with unreserved Line Items. - Reserve stock by changing the inventory mode of each Line Item. - Check for warnings indicating unsuccessful reservations. - Freeze the prices, discounts, and shipping costs of Line Items. ## Prerequisites Before you begin, you should be familiar with: - Concepts such as [Products](/api/projects/products.md#product), [Carts](/api/projects/carts.md#cart), and [Inventory](/api/projects/inventory.md#inventoryentry). - Using a REST API client such as Postman or Insomnia to interact with the Composable Commerce API. - General concepts around [Project setup](/merchant-center/organizations-teams-projects.md#manage-projects) and [API Client management](/merchant-center/developer-settings.md#api-clients) in the Merchant Center. - The `ReserveOnCart` inventory mode, including [how reservations work](/api/inventory-overview.md#reservations) and their implications. You should also have a [commercetools Project](/api/projects/project.md#project) with the following: - **A Product**: Create a [Product](/search.md?urn=ctp:api:type:Product) with at least one [Product Variant](/api/projects/products.md#productvariant). Note the `SKU` of the variant. - **API Client**: An [API Client](/api/projects/api-clients.md#apiclient) with the following [scopes](/api/scopes.md): - `manage_orders:{projectKey}` to create and update Carts and Orders. - `manage_project_settings:{projectKey}` to update reservation-related Project settings. - `manage_products:{projectKey}` to create and update Inventory Entries. All examples in this tutorial use [cURL](https://curl.se/), but you can adapt them to your preferred tool. ## Instructions Consider a scenario in which you want to ensure a customer successfully completes the checkout of their Cart. You can use the following two features together to achieve this: - [Freezing a Cart](/api/carts-orders-overview.md#freeze-a-cart): prevents price changes to the Cart during checkout, depending on the chosen [freeze strategy](/api/carts-orders-overview.md#freeze-a-cart). - `ReserveOnCart` [InventoryMode](/search.md?urn=ctp:api:type:InventoryMode): reserves items in the Cart. Reservations remain active until the Cart is ordered or the reservation expires. You can apply this inventory mode in one of two ways: - **On the Cart**: all Line Items are reserved automatically. This is useful when you want to reserve stock for all items as soon as they are added to the Cart. - **On individual Line Items**: some or all Line Items are reserved, but at a specific point in time that you choose. This approach gives you more control over when to reserve stock during your checkout flow. This tutorial focuses on setting the inventory mode on individual Line Items, allowing you to reserve stock on demand. ### Benefits of combining freezing with reservations Without freezing, a customer's Cart total can change between adding items and completing checkout. For example, a price promotion could end, a discount could be removed, or shipping costs could increase. This creates an unpredictable checkout experience. Similarly, without reserving stock, items can become unavailable during checkout. For example, a customer has an item in their Cart and is actively checking out. Another customer comes along and purchases the last available item, causing the first customer's order to fail. By combining both features, you ensure that: - The Cart total remains the same during the entire checkout flow. - Stock is guaranteed to be available when you create the Order. This gives customers confidence that Cart prices and items remain accurate throughout checkout. To implement this, use the `setLineItemInventoryMode` update action to reserve stock on individual Line Items, then freeze prices with `freezeCart`. Before doing that, enable reservations on your Project, then create an Inventory Entry and a Cart. ### Set the Project-level expiration Before you can use reservations, you must set the Project-level reservation expiration time. After you set this default, you can use the `ReserveOnCart` [InventoryMode](/search.md?urn=ctp:api:type:InventoryMode). This is a mandatory step to ensure that stock is not held indefinitely, which could lead to it being unavailable for other customers. You only have to set this once per Project. To do this, use the [Set Reservation Expiration in Minutes](/api/projects/project.md#set-reservation-expiration-in-minutes) update action on the [Project](/api/projects/project.md). Specify a value between 1 and 44640 minutes (31 days). ```bash title="Set Project-level reservation expiration" curl -X POST "https://api.europe-west1.gcp.commercetools.com/{{project-key}}" -H "Authorization: Bearer {{auth-token}}" -H "Content-Type: application/json" -d '{ "version": {{project-version}}, "actions": [ { "action": "setReservationExpirationInMinutes", "reservationExpirationInMinutes": 30 } ] }' ``` You can override this value by optionally using the [Set Reservation Expiration in Minutes](/api/projects/inventory.md#set-reservation-expiration-in-minutes) update action on an [Inventory Entry](/api/projects/inventory.md#inventoryentry). ### Create an Inventory Entry To reserve stock you must first have an inventory entry to track the available stock. The inventory entry must have the same `sku` as your Product Variant (`SKU-1` in this example). Set the `quantityOnStock` to `10`. ```bash title="Create an Inventory Entry" curl -X POST "https://api.europe-west1.gcp.commercetools.com/{{project-key}}/inventory" -H "Authorization: Bearer {{auth-token}}" -H "Content-Type: application/json" -d '{ "sku": "SKU-1", "quantityOnStock": 10 }' ``` ### Create a Cart Create a Cart without reserving stock immediately. Omit the `inventoryMode` property so that Line Items use the default mode: `None`. ```bash title="Create a Cart without reserving stock" curl -X POST "https://api.europe-west1.gcp.commercetools.com/{{project-key}}/carts" -H "Authorization: Bearer {{auth-token}}" -H "Content-Type: application/json" -d '{ "currency": "USD", "lineItems": [ { "sku": "SKU-1", "quantity": 1 } ] }' ``` #### Verify no reservation was made Since the Inventory Mode has not yet been set to `ReserveOnCart`, no stock has been reserved. Verify stock availability by checking the `availableQuantity` of the Inventory Entry. It will still be equal to `10`. ```bash title="Get Inventory Entry" curl -X GET "https://api.europe-west1.gcp.commercetools.com/{{project-key}}/inventory/{{inventory-id}}" -H "Authorization: Bearer {{auth-token}}" ``` ### Reserve stock When the customer reaches a desired point in the checkout flow, you can reserve stock for the items in the Cart. After that, you can freeze the Cart to lock in prices, discounts, and shipping costs. Reserve stock before freezing to ensure that stock is actually available. To reserve stock for a specific Line Item, use the `setLineItemInventoryMode` update action. For Carts with multiple Line Items, add a `setLineItemInventoryMode` update action **for each Line Item** that you want to reserve. ```bash title="Reserve stock on the Line Item" curl -X POST "https://api.europe-west1.gcp.commercetools.com/{{project-key}}/carts/{{cart-id}}" -H "Authorization: Bearer {{auth-token}}" -H "Content-Type: application/json" -d '{ "version": {{cart-version}}, "actions": [ { "action": "setLineItemInventoryMode", "lineItemId": "{{line-item-id}}", "inventoryMode": "ReserveOnCart" } ] }' ``` #### Verify the reservation You can verify that the reservation was successful by checking the response. If all of the reservations were successfully created, then the `inventoryMode` of each Line Item will be `ReserveOnCart`. The absence of any warnings is also a good indicator that everything worked as expected. If a Line Item's inventory mode is not `ReserveOnCart` and the Cart's `warnings` field contains a [CannotCreateReservationWarning](/api/types.md#cannotcreatereservation-warning), then the reservation was unsuccessful. You'll need to handle these warnings before proceeding to freeze the Cart. When a reservation fails, you have several options: - [Increase the quantity](/api/projects/inventory.md#add-quantity) of the low stock inventory entry and retry the `setLineItemInventoryMode` update action. - Set the `inventoryMode` of the low stock line item to `TrackOnly`. Using this inventory mode, an Order can be created even if there is insufficient stock. The line item `quantity` will be deducted from the inventory entry quantity when ordering, which means the inventory can go negative. This is the intended behavior when using `TrackOnly` as a fallback. - [Remove](/api/projects/carts.md#remove-lineitem) the low stock line item from the Cart. You can alternatively use this same update action to reduce the `quantity` to a value that doesn't exceed the inventory entry's `availableQuantity`. - [Set `restockableInDays`](/api/projects/inventory.md#set-restockableindays) on the inventory entry and retry `setLineItemInventoryMode`. Reservations using `ReserveOnCart` or `ReserveOnOrder` will succeed, even when there is insufficient stock, if `restockableInDays` is defined. ### Freeze the Cart If the reservations were successful, you can now freeze the Cart using one of two [freeze strategies](/api/carts-orders-overview.md#freeze-a-cart): `SoftFreeze` or `HardFreeze`. Using the `HardFreeze` strategy ensures shipping costs and all discounts are frozen, which guarantees that the Cart total doesn't change during checkout. ```bash title="Freeze the Cart" curl -X POST "https://api.europe-west1.gcp.commercetools.com/{{project-key}}/carts/{{cart-id}}" -H "Authorization: Bearer {{auth-token}}" -H "Content-Type: application/json" -d '{ "version": {{cart-version}}, "actions": [ { "action": "freezeCart", "strategy": "HardFreeze" } ] }' ``` You can verify that the Cart is frozen by checking the response. The `cartState` field should be set to `Frozen`. At this point, stock is reserved for your specified Line Items and all prices, discounts, and shipping costs are frozen. ### Create the Order Before the reservations expire, you must create an Order from the Cart. When you do this, the reserved stock is permanently deducted from inventory. If you don't create an Order before the reservation expiration time, the stock will be released and become available for other Carts to reserve. ### Best practices To ensure a smooth checkout process, you should: - **Handle unsuccessful reservations before ordering**: if any Line Item's `inventoryMode` was not updated to `ReserveOnCart`, the inventory entry's stock will not be deducted when you create the Order. - **Set appropriate expiration times**: to prevent Denial of Stock attacks, all reservations have a mandatory expiration time between 1 and 44640 minutes (31 days). Configure a `reservationExpirationInMinutes` value that gives customers enough time to complete checkout without holding stock unnecessarily long. When a Line Item reservation expires, the stock becomes available again for other Carts to reserve. - **Extend expiration times proactively**: if a customer's checkout takes longer than expected, [extend the expiration](/api/projects/carts.md#set-reservation-expiration-in-minutes) of every Line Item reservation in the Cart before they expire. This prevents the Cart from becoming unorderable. - **Check reservation status**: before ordering a Cart, check for reservations that have expired or are about to expire. Use [reference expansion](/api/general-concepts.md#reference-expansion) on the `reservation` field of a Line Item to check the reservation validity. - **Handle re-reservation failures**: the Cart will automatically attempt to re-reserve stock when creating an Order. If the stock is no longer available, then creating the Order will fail with an `OutOfStock` error. Use the same logic to handle reservation failures that can occur when renewing expired Line Item reservations. - **Account for eventual consistency**: changes to the `availableQuantity` and `quantityOnStock` fields resulting from reservation operations are [eventually consistent](/api/general-concepts.md#eventual-consistency) and may take up to 10 seconds to be reflected. See [Inventory checks and consistency](/api/inventory-overview.md#inventory-checks-and-consistency) for details on how this affects your implementation. ## Summary You have learned how to: - Enable reservation functionality in your Project. - Reserve stock on demand, at any stage during your checkout process. - Check for warnings indicating unsuccessful reservations. - Handle failures when reserving stock. - Freeze prices, discounts, and shipping costs to ensure a consistent checkout experience. This approach gives you flexibility in when to commit inventory, allowing you to balance customer experience with inventory management needs. When implementing this in a real project, consider your specific checkout flow and adjust the timing of reservations accordingly. ## Further reading To learn more about implementing reservations, see the following resources: - [Reserve stock on Cart](/tutorials/reserve-stock-on-cart.md): prevent overselling and improve the checkout experience by automatically reserving items in a Cart. - [Inventory overview](/api/inventory-overview.md): information about managing inventory, reservations, and stock levels. ## Related pages - [Area overview page with navigation](/tutorials.md) - [Previous page: Reserve stock on Cart](/tutorials/reserve-stock-on-cart.md) - [Next page: Multiple Shipping Addresses and Methods](/tutorials/multiple-shipping-addresses-methods.md) - [Search documentation and API specs](/search.md)