diff --git a/packages/evershop/src/components/frontStore/checkout/cart/items/Items.jsx b/packages/evershop/src/components/frontStore/checkout/cart/items/Items.jsx
index 5e6450b20..3e1c5b7ce 100644
--- a/packages/evershop/src/components/frontStore/checkout/cart/items/Items.jsx
+++ b/packages/evershop/src/components/frontStore/checkout/cart/items/Items.jsx
@@ -7,10 +7,10 @@ import ProductNoThumbnail from '@components/common/ProductNoThumbnail';
 import { ItemOptions } from './ItemOptions';
 import { ItemVariantOptions } from './ItemVariantOptions';
 import './Items.scss';
+import Quantity from './Quantity';
 
 function Items({ items, setting: { priceIncludingTax } }) {
   const AppContextDispatch = useAppDispatch();
-
   const removeItem = async (item) => {
     const response = await fetch(item.removeApi, {
       method: 'DELETE',
@@ -124,13 +124,12 @@ function Items({ items, setting: { priceIncludingTax } }) {
                     </span>
                   </div>
                 )}
-                <div className="md:hidden mt-2">
-                  <span>{_('Qty')}</span>
-                  <span>{item.qty}</span>
+                <div className="md:hidden mt-2 flex justify-end">
+                  <Quantity qty={item.qty} api={item.updateQtyApi} />
                 </div>
               </td>
               <td className="hidden md:table-cell">
-                <span>{item.qty}</span>
+                <Quantity qty={item.qty} api={item.updateQtyApi} />
               </td>
               <td className="hidden md:table-cell">
                 <span>
@@ -180,7 +179,8 @@ Items.propTypes = {
         value: PropTypes.number,
         text: PropTypes.string
       }),
-      removeApi: PropTypes.string
+      removeApi: PropTypes.string,
+      updateQtyApi: PropTypes.string
     })
   ).isRequired,
   setting: PropTypes.shape({
diff --git a/packages/evershop/src/components/frontStore/checkout/cart/items/Items.scss b/packages/evershop/src/components/frontStore/checkout/cart/items/Items.scss
index bd84a8c59..c807a4a8a 100644
--- a/packages/evershop/src/components/frontStore/checkout/cart/items/Items.scss
+++ b/packages/evershop/src/components/frontStore/checkout/cart/items/Items.scss
@@ -47,4 +47,51 @@
       }
     }
   }
+  .qty-box {
+    max-width: 130px;
+    button {
+      .spinner {
+        animation: rotator 1.4s linear infinite;
+        .path {
+          stroke-dasharray: 280;
+          stroke-dashoffset: 0;
+          transform-origin: center;
+          stroke: var(--primary);
+          animation: dash 1.4s ease-in-out infinite;
+        }
+      }
+      svg {
+        width: 1.1rem;
+        height: 1.1rem;
+      }
+    }
+    input {
+      text-align: center;
+      padding-left: 10px;
+      padding-right: 10px;
+    }
+  }
 }
+
+@keyframes rotator {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(270deg);
+  }
+}
+
+@keyframes dash {
+  0% {
+    stroke-dashoffset: 280;
+  }
+  50% {
+    stroke-dashoffset: 70;
+    transform: rotate(135deg);
+  }
+  100% {
+    stroke-dashoffset: 280;
+    transform: rotate(450deg);
+  }
+}
\ No newline at end of file
diff --git a/packages/evershop/src/components/frontStore/checkout/cart/items/Quantity.jsx b/packages/evershop/src/components/frontStore/checkout/cart/items/Quantity.jsx
new file mode 100644
index 000000000..369bad748
--- /dev/null
+++ b/packages/evershop/src/components/frontStore/checkout/cart/items/Quantity.jsx
@@ -0,0 +1,154 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useAppDispatch } from '@components/common/context/app';
+import { toast } from 'react-toastify';
+
+export default function Quantity({ qty, api }) {
+  const AppContextDispatch = useAppDispatch();
+  const [quantity, setQuantity] = React.useState(qty);
+  const previousQuantity = React.useRef(qty);
+  const [debounceTimer, setDebounceTimer] = React.useState(null);
+  const [isLoading, setIsLoading] = React.useState(false);
+
+  const updateQuantity = (newQuantity) => {
+    setQuantity(newQuantity);
+    if (debounceTimer) {
+      clearTimeout(debounceTimer);
+    }
+    const timer = setTimeout(() => {
+      callUpdateAPI(newQuantity);
+    }, 500);
+    setDebounceTimer(timer);
+  };
+
+  const callUpdateAPI = async (qty) => {
+    setIsLoading(true);
+    try {
+      const response = await fetch(api, {
+        method: 'PATCH',
+        headers: {
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          qty: Math.abs(previousQuantity.current - qty),
+          action: qty > quantity ? 'increase' : 'decrease'
+        }),
+        credentials: 'same-origin'
+      });
+      const json = await response.json();
+      if (!json.error) {
+        const url = new URL(window.location.href);
+        url.searchParams.set('ajax', true);
+        await AppContextDispatch.fetchPageData(url);
+        previousQuantity.current = qty;
+      } else {
+        setQuantity(previousQuantity.current);
+        toast.error(json.error.message);
+      }
+    } catch (error) {
+      setQuantity(previousQuantity.current);
+      toast.error(error.message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="qty-box grid grid-cols-3 border border-[#ccc]">
+      <button
+        className="flex justify-center items-center"
+        onClick={() => updateQuantity(Math.max(quantity - 1, 0))}
+        disabled={isLoading}
+        type="button"
+      >
+        {isLoading && (
+          <svg
+            ariaHidden="true"
+            focusable="false"
+            role="presentation"
+            className="spinner"
+            viewBox="0 0 66 66"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <circle
+              className="path"
+              fill="none"
+              strokeWidth="6"
+              cx="33"
+              cy="33"
+              r="30"
+            />
+          </svg>
+        )}
+        {!isLoading && (
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            ariaHidden="true"
+            focusable="false"
+            role="presentation"
+            className="icon icon-minus"
+            fill="none"
+            viewBox="0 0 10 2"
+          >
+            <path
+              fillRule="evenodd"
+              clipRule="evenodd"
+              d="M.5 1C.5.7.7.5 1 .5h8a.5.5 0 110 1H1A.5.5 0 01.5 1z"
+              fill="currentColor"
+            />
+          </svg>
+        )}
+      </button>
+      <input type="text" value={quantity} />
+      <button
+        className="flex justify-center items-center"
+        onClick={() => updateQuantity(quantity + 1)}
+        disabled={isLoading}
+        type="button"
+      >
+        {isLoading && (
+          <svg
+            ariaHidden="true"
+            focusable="false"
+            role="presentation"
+            className="spinner"
+            viewBox="0 0 66 66"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <circle
+              className="path"
+              fill="none"
+              strokeWidth="6"
+              cx="33"
+              cy="33"
+              r="30"
+            />
+          </svg>
+        )}
+        {!isLoading && (
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            ariaHidden="true"
+            focusable="false"
+            role="presentation"
+            className="icon icon-plus"
+            fill="none"
+            viewBox="0 0 10 10"
+          >
+            <path
+              fillRule="evenodd"
+              clipRule="evenodd"
+              d="M1 4.51a.5.5 0 000 1h3.5l.01 3.5a.5.5 0 001-.01V5.5l3.5-.01a.5.5 0 00-.01-1H5.5L5.49.99a.5.5 0 00-1 .01v3.5l-3.5.01H1z"
+              fill="currentColor"
+            />
+          </svg>
+        )}
+      </button>
+    </div>
+  );
+}
+
+Quantity.propTypes = {
+  qty: PropTypes.number.isRequired,
+  api: PropTypes.string.isRequired
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateCartItemQty/[bodyParser]updateQty.js b/packages/evershop/src/modules/checkout/api/updateCartItemQty/[bodyParser]updateQty.js
new file mode 100644
index 000000000..3edddcd12
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateCartItemQty/[bodyParser]updateQty.js
@@ -0,0 +1,49 @@
+const {
+  INVALID_PAYLOAD,
+  INTERNAL_SERVER_ERROR,
+  OK
+} = require('@evershop/evershop/src/lib/util/httpStatus');
+const {
+  translate
+} = require('@evershop/evershop/src/lib/locale/translate/translate');
+const { error } = require('@evershop/evershop/src/lib/log/logger');
+const { getCartByUUID } = require('../../services/getCartByUUID');
+const { saveCart } = require('../../services/saveCart');
+
+module.exports = async (request, response, delegate, next) => {
+  try {
+    const { cart_id, item_id } = request.params;
+    const cart = await getCartByUUID(cart_id);
+    if (!cart) {
+      response.status(INVALID_PAYLOAD);
+      response.json({
+        error: {
+          message: translate('Invalid cart'),
+          status: INVALID_PAYLOAD
+        }
+      });
+      return;
+    }
+    const { action, qty } = request.body;
+    const item = await cart.updateItemQty(item_id, qty, action);
+    await saveCart(cart);
+    response.status(OK);
+    response.$body = {
+      data: {
+        item: item.export(),
+        count: cart.getItems().length,
+        cartId: cart.getData('uuid')
+      }
+    };
+    next();
+  } catch (err) {
+    error(err);
+    response.status(INTERNAL_SERVER_ERROR);
+    response.json({
+      error: {
+        status: INTERNAL_SERVER_ERROR,
+        message: err.message
+      }
+    });
+  }
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateCartItemQty/[context]bodyParser[auth].js b/packages/evershop/src/modules/checkout/api/updateCartItemQty/[context]bodyParser[auth].js
new file mode 100644
index 000000000..1f3c47e69
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateCartItemQty/[context]bodyParser[auth].js
@@ -0,0 +1,5 @@
+const bodyParser = require('body-parser');
+
+module.exports = (request, response, delegate, next) => {
+  bodyParser.json({ inflate: false })(request, response, next);
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateCartItemQty/payloadSchema.json b/packages/evershop/src/modules/checkout/api/updateCartItemQty/payloadSchema.json
new file mode 100644
index 000000000..3f04b4a2c
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateCartItemQty/payloadSchema.json
@@ -0,0 +1,21 @@
+{
+  "type": "object",
+  "properties": {
+    "action": {
+      "type": "string",
+      "enum": ["increase", "decrease"] 
+    },
+    "qty": {
+      "type": ["string", "integer"],
+      "pattern": "^[1-9][0-9]*$"
+    }
+  },
+  "required": ["action", "qty"],
+  "additionalProperties": true,
+  "errorMessage": {
+    "properties": {
+      "action": "Action is required. It must be either 'increase' or 'decrease'",
+      "qty": "Qty is invalid"
+    }
+  }
+}
diff --git a/packages/evershop/src/modules/checkout/api/updateCartItemQty/route.json b/packages/evershop/src/modules/checkout/api/updateCartItemQty/route.json
new file mode 100644
index 000000000..eb1505cb2
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateCartItemQty/route.json
@@ -0,0 +1,5 @@
+{
+  "methods": ["PATCH"],
+  "path": "/cart/:cart_id/items/:item_id",
+  "access": "public"
+}
diff --git a/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[context]bodyParser[auth].js b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[context]bodyParser[auth].js
new file mode 100644
index 000000000..1f3c47e69
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[context]bodyParser[auth].js
@@ -0,0 +1,5 @@
+const bodyParser = require('body-parser');
+
+module.exports = (request, response, delegate, next) => {
+  bodyParser.json({ inflate: false })(request, response, next);
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[detectCurrentCart]updateQty.js b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[detectCurrentCart]updateQty.js
new file mode 100644
index 000000000..8a93abb66
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[detectCurrentCart]updateQty.js
@@ -0,0 +1,51 @@
+const {
+  INVALID_PAYLOAD,
+  INTERNAL_SERVER_ERROR,
+  OK
+} = require('@evershop/evershop/src/lib/util/httpStatus');
+const {
+  translate
+} = require('@evershop/evershop/src/lib/locale/translate/translate');
+const { error } = require('@evershop/evershop/src/lib/log/logger');
+const { getCartByUUID } = require('../../services/getCartByUUID');
+const { saveCart } = require('../../services/saveCart');
+const { getContextValue } = require('../../../graphql/services/contextHelper');
+
+module.exports = async (request, response, delegate, next) => {
+  try {
+    const { item_id } = request.params;
+    const cartId = getContextValue(request, 'cartId');
+    const cart = await getCartByUUID(cartId);
+    if (!cart) {
+      response.status(INVALID_PAYLOAD);
+      response.json({
+        error: {
+          message: translate('Invalid cart'),
+          status: INVALID_PAYLOAD
+        }
+      });
+      return;
+    }
+    const { action, qty } = request.body;
+    const item = await cart.updateItemQty(item_id, qty, action);
+    await saveCart(cart);
+    response.status(OK);
+    response.$body = {
+      data: {
+        item: item.export(),
+        count: cart.getItems().length,
+        cartId: cart.getData('uuid')
+      }
+    };
+    next();
+  } catch (err) {
+    error(err);
+    response.status(INTERNAL_SERVER_ERROR);
+    response.json({
+      error: {
+        status: INTERNAL_SERVER_ERROR,
+        message: err.message
+      }
+    });
+  }
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[getCurrentCustomer]detectCurrentCart.js b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[getCurrentCustomer]detectCurrentCart.js
new file mode 100644
index 000000000..01002505b
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[getCurrentCustomer]detectCurrentCart.js
@@ -0,0 +1,32 @@
+const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { select } = require('@evershop/postgres-query-builder');
+const { UNAUTHORIZED } = require('@evershop/evershop/src/lib/util/httpStatus');
+const { setContextValue } = require('../../../graphql/services/contextHelper');
+
+module.exports = async (request, response, delegate, next) => {
+  /**
+   * We firstly get the sessionID from the request.
+   * This API needs the client to send the session cookie in the request.
+   * Base on the sessionID, we can get the cart
+   */
+  const { sessionID } = request.locals;
+  if (!sessionID) {
+    response.status(UNAUTHORIZED);
+    response.json({
+      error: {
+        status: UNAUTHORIZED,
+        message: 'Unauthorized'
+      }
+    });
+  } else {
+    const cart = await select()
+      .from('cart')
+      .where('sid', '=', sessionID)
+      .and('status', '=', 1)
+      .load(pool);
+    if (cart) {
+      setContextValue(request, 'cartId', cart.uuid);
+    }
+    next();
+  }
+};
diff --git a/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/payloadSchema.json b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/payloadSchema.json
new file mode 100644
index 000000000..3f04b4a2c
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/payloadSchema.json
@@ -0,0 +1,21 @@
+{
+  "type": "object",
+  "properties": {
+    "action": {
+      "type": "string",
+      "enum": ["increase", "decrease"] 
+    },
+    "qty": {
+      "type": ["string", "integer"],
+      "pattern": "^[1-9][0-9]*$"
+    }
+  },
+  "required": ["action", "qty"],
+  "additionalProperties": true,
+  "errorMessage": {
+    "properties": {
+      "action": "Action is required. It must be either 'increase' or 'decrease'",
+      "qty": "Qty is invalid"
+    }
+  }
+}
diff --git a/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/route.json b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/route.json
new file mode 100644
index 000000000..4cd73ac0e
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/api/updateMineCartItemQty/route.json
@@ -0,0 +1,5 @@
+{
+  "methods": ["PATCH"],
+  "path": "/cart/mine/items/:item_id",
+  "access": "public"
+}
diff --git a/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.graphql b/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.graphql
index 09d6ff684..7ee962123 100644
--- a/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.graphql
+++ b/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.graphql
@@ -97,6 +97,7 @@ type CartItem implements ShoppingCartItem {
   uuid: String!
   cartId: ID!
   removeApi: String!
+  updateQtyApi: String!
   productId: ID!
   productSku: String!
   productName: String
diff --git a/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.resolvers.js b/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.resolvers.js
index 5d769da4d..7873b845f 100644
--- a/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.resolvers.js
+++ b/packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.resolvers.js
@@ -19,7 +19,14 @@ module.exports = {
       const items = cart.items || [];
       return items.map((item) => ({
         ...camelCase(item),
-        removeApi: buildUrl('removeMineCartItem', { item_id: item.uuid })
+        removeApi: buildUrl('removeCartItem', {
+          item_id: item.uuid,
+          cart_id: cart.uuid
+        }),
+        updateQtyApi: buildUrl('updateCartItemQty', {
+          cart_id: cart.uuid,
+          item_id: item.uuid
+        })
       }));
     },
     shippingAddress: async ({ shippingAddressId }, _, { pool }) => {
@@ -46,13 +53,11 @@ module.exports = {
     addAddressApi: (cart) => buildUrl('addCartAddress', { cart_id: cart.uuid })
   },
   CartItem: {
-    total: ({ lineTotalInclTax }) => 
+    total: ({ lineTotalInclTax }) =>
       // This field is deprecated, use lineTotalInclTax instead
-       lineTotalInclTax
-    ,
-    subTotal: ({ lineTotal }) => 
+      lineTotalInclTax,
+    subTotal: ({ lineTotal }) =>
       // This field is deprecated, use lineTotal instead
-       lineTotal
-    
+      lineTotal
   }
 };
diff --git a/packages/evershop/src/modules/checkout/migration/Version-1.0.6.js b/packages/evershop/src/modules/checkout/migration/Version-1.0.6.js
index 27c0df39d..8d9c7f935 100644
--- a/packages/evershop/src/modules/checkout/migration/Version-1.0.6.js
+++ b/packages/evershop/src/modules/checkout/migration/Version-1.0.6.js
@@ -108,7 +108,7 @@ module.exports = exports = async (connection) => {
     'ALTER TABLE "order" ADD COLUMN IF NOT EXISTS "shipping_tax_amount" decimal(12,4)'
   );
 
-  // Calculate the value for the new columns 'tax_amount_before_discount', 'line_total_with_discount', 'line_total_with_discount_incl_tax', 'sub_total_before_discount', 'sub_total_before_discount_incl_tax'
+  // Calculate the value for the new columns 'tax_amount_before_discount', 'line_total_with_discount', 'line_total_with_discount_incl_tax', 'sub_total'
 
   await execute(
     connection,
@@ -178,6 +178,6 @@ module.exports = exports = async (connection) => {
 
   await execute(
     connection,
-    `UPDATE "order" SET sub_total_before_discount_incl_tax = sub_total_with_discount + tax_amount`
+    `UPDATE "order" SET sub_total_with_discount_incl_tax = sub_total_with_discount + tax_amount`
   );
 };
diff --git a/packages/evershop/src/modules/checkout/pages/frontStore/cart/ShoppingCart.jsx b/packages/evershop/src/modules/checkout/pages/frontStore/cart/ShoppingCart.jsx
index 14564343e..485f14208 100644
--- a/packages/evershop/src/modules/checkout/pages/frontStore/cart/ShoppingCart.jsx
+++ b/packages/evershop/src/modules/checkout/pages/frontStore/cart/ShoppingCart.jsx
@@ -133,6 +133,7 @@ export const query = `
           text
         }
         removeApi
+        updateQtyApi
         errors
       }
     }
diff --git a/packages/evershop/src/modules/checkout/services/cart/Cart.js b/packages/evershop/src/modules/checkout/services/cart/Cart.js
index 4c568b65c..7e59c2679 100644
--- a/packages/evershop/src/modules/checkout/services/cart/Cart.js
+++ b/packages/evershop/src/modules/checkout/services/cart/Cart.js
@@ -113,6 +113,29 @@ class Cart extends DataObject {
     }
   }
 
+  async updateItemQty(uuid, qty, action) {
+    if (['increase', 'decrease'].indexOf(action) === -1) {
+      throw new Error('Invalid action');
+    }
+    const item = this.getItem(uuid);
+    if (!item) {
+      throw new Error('Item not found');
+    }
+    if (action === 'increase') {
+      await item.setData('qty', item.getData('qty') + parseInt(qty, 10));
+    } else {
+      const currentQty = item.getData('qty');
+      const newQty = Math.max(currentQty - parseInt(qty, 10), 0);
+      if (newQty === 0) {
+        await this.removeItem(uuid);
+      } else {
+        await item.setData('qty', newQty);
+      }
+    }
+    await this.build();
+    return item;
+  }
+
   async createItem(productId, qty) {
     // Make sure the qty is a number, not NaN and greater than 0
     if (typeof qty !== 'number' || Number.isNaN(qty) || qty <= 0) {