Body: <p>In Craft Commerce, I am using the Blitz Cache plugin. My &quot;add to cart&quot;-button uses ajax to add the product to the cart.</p>
  <p>This is the twig-code for the form:</p>
  <pre class="lang-html prettyprint-override"><code>&lt;form method=&quot;post&quot; class=&quot;product-form&quot;&gt;
    {{ csrfInput() }}
    {{ actionInput('commerce/cart/update-cart') }}
    {{ successMessageInput('{product} added to cart.'|t({
      product: product.title
    })) }}
    &lt;section class=&quot;actions&quot;&gt;
          &lt;input type=&quot;hidden&quot; name=&quot;purchasableId&quot; value=&quot;{{ defaultVariant.id }}&quot; /&gt;
          &lt;input type=&quot;number&quot; value=&quot;1&quot; name=&quot;qty&quot; class=&quot;form-control&quot; /&gt;
          &lt;button type=&quot;button&quot; class=&quot;btn btn-primary btn-add-to-basket&quot;&gt;&lt;i class=&quot;icon icon-cart-plus icon-lg&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;Add to basket&lt;/button&gt;
    &lt;/section&gt;
  &lt;/form&gt;
  </code></pre>
  <p>This is the javascript that takes over the click event for the add-to-basket  button and posts via ajax:</p>
  <pre class="lang-js prettyprint-override"><code>$(&quot;.btn-add-to-basket&quot;).on(&quot;click&quot;, function(e){
      $form = $(e.target).closest(&quot;.product-form&quot;);
  
      // refresh csrf token
      fetch('/api/cart/csrf')
          .then(response =&gt; response.json())
          .then(data =&gt; {
            $form.find('input[name=&quot;' + data.csrfTokenName + '&quot;]').val(data.csrfTokenValue);
  
            window.csrfTokenName = data.csrfTokenName;
            window.csrfTokenValue = data.csrfTokenValue;
            
            var form_data = $form.serializeArray()
  
            // form_data.push({
            //     name: window.csrfTokenName,
            //     value: window.csrfTokenValue
            // })
      
            $.ajax({
              type: 'post',
              url: &quot;/commerce/cart/updateCart&quot;,
              dataType: &quot;json&quot;,
              data: form_data,
      
              success: function (response, textStatus, jqXHR) {      
                  // console.info(jqXHR);            
                  if (jqXHR.status === 200){
                    updateCart(); // get new cart count and update UI
                  }
              },
              error: function(request, status, error) {
                  // Handle failure
                  console.error(&quot;failed&quot; + error);
              }
            });         
  
          }).catch(error =&gt; console.error('Error fetching CSRF token:', error));
  
    });
  </code></pre>
  <p>This is the action code for the /api/cart/csrf:</p>
  <pre class="lang-php prettyprint-override"><code>public function actionCsrf(): Response
  {
      return $this-&gt;asJson([
          'csrfTokenName' =&gt; Craft::$app-&gt;getConfig()-&gt;getGeneral()-&gt;csrfTokenName,
          'csrfTokenValue' =&gt; Craft::$app-&gt;getRequest()-&gt;getCsrfToken(),
      ]);
  }
  </code></pre>
  <p>My problem is that sometimes the call to <code>/commerce/cart/updateCart</code> will return &quot;invalid csrf&quot; and I experience a lot of times that the cart simply becomes abandoned and a new cart is created instead.</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2024-04-11T10:26:51.877Z
Comments.Text: Just a couple of comments: 1) Forms can be submitted without necessarily clicking a submit button (e.g. by pressing enter while inside a field) - so you should never attach important behaviour to the click event - use the form's submit event instead. 2) Using static caching on an eCommerce site seems pretty dangerous to me (i.e., ensuring that no customer sees another customer's personal details or order history or cart contents), but if you know what you're doing then fair enough!! I've always been tempted to do static caching and create "dynamic islands" for eCommerce but not tried it yet!
Comments.UserDisplayName: James Smith
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2024-04-11T11:34:24.530Z
Comments.Text: Thanks James. Good point about attaching to the form's onSubmit event instead. I am not too spooked about using caching on an e-commerce site. The Blitz cache plugin allows you to blacklist URL patterns to exclude them from caching, which I've done to exclude everything that happens during /shop/checkout
Comments.UserDisplayName: nitech
Tags: <plugin-craftcommerce><ajax><csrf><plugin-blitz>
Title: Add to cart via Ajax (sometimes) results in abandoned carts and invalid CSRF
ThreadText: # Add to cart via Ajax (sometimes) results in abandoned carts and invalid CSRF
  
  (Asked by: username_0 on 2024-04-11)
  
  In Craft Commerce, I am using the Blitz Cache plugin. My "add to cart"-button uses ajax to add the product to the cart.
  
  This is the twig-code for the form:
  
  ```html
  <form method="post" class="product-form">
    {{ csrfInput() }}
    {{ actionInput('commerce/cart/update-cart') }}
    {{ successMessageInput('{product} added to cart.'|t({
      product: product.title
    })) }}
    <section class="actions">
          <input type="hidden" name="purchasableId" value="{{ defaultVariant.id }}" />
          <input type="number" value="1" name="qty" class="form-control" />
          <button type="button" class="btn btn-primary btn-add-to-basket"><i class="icon icon-cart-plus icon-lg" aria-hidden="true"></i>Add to basket</button>
    </section>
  </form>
  
  ```
  
  This is the javascript that takes over the click event for the add-to-basket button and posts via ajax:
  
  ```js
  $(".btn-add-to-basket").on("click", function(e){
      $form = $(e.target).closest(".product-form");
  
      // refresh csrf token
      fetch('/api/cart/csrf')
          .then(response => response.json())
          .then(data => {
            $form.find('input[name="' + data.csrfTokenName + '"]').val(data.csrfTokenValue);
  
            window.csrfTokenName = data.csrfTokenName;
            window.csrfTokenValue = data.csrfTokenValue;
            
            var form_data = $form.serializeArray()
  
            // form_data.push({
            //     name: window.csrfTokenName,
            //     value: window.csrfTokenValue
            // })
      
            $.ajax({
              type: 'post',
              url: "/commerce/cart/updateCart",
              dataType: "json",
              data: form_data,
      
              success: function (response, textStatus, jqXHR) {      
                  // console.info(jqXHR);            
                  if (jqXHR.status === 200){
                    updateCart(); // get new cart count and update UI
                  }
              },
              error: function(request, status, error) {
                  // Handle failure
                  console.error("failed" + error);
              }
            });         
  
          }).catch(error => console.error('Error fetching CSRF token:', error));
  
    });
  
  ```
  
  This is the action code for the /api/cart/csrf:
  
  ```php
  public function actionCsrf(): Response
  {
      return $this->asJson([
          'csrfTokenName' => Craft::$app->getConfig()->getGeneral()->csrfTokenName,
          'csrfTokenValue' => Craft::$app->getRequest()->getCsrfToken(),
      ]);
  }
  
  ```
  
  My problem is that sometimes the call to `/commerce/cart/updateCart` will return "invalid csrf" and I experience a lot of times that the cart simply becomes abandoned and a new cart is created instead.
  
  **Tags:** plugin-craftcommerce, ajax, csrf, plugin-blitz
  
  > **Comments:**
  > **username_1**: Just a couple of comments: 1) Forms can be submitted without necessarily clicking a submit button (e.g. by pressing enter while inside a field) - so you should never attach important behaviour to the click event - use the form's submit event instead. 2) Using static caching on an eCommerce site seems pretty dangerous to me (i.e., ensuring that no customer sees another customer's personal details or order history or cart contents), but if you know what you're doing then fair enough!! I've always been tempted to do static caching and create "dynamic islands" for eCommerce but not tried it yet!  
  > **username_0**: Thanks username_1. Good point about attaching to the form's onSubmit event instead. I am not too spooked about using caching on an e-commerce site. The Blitz cache plugin allows you to blacklist URL patterns to exclude them from caching, which I've done to exclude everything that happens during /shop/checkout  
  
  ---
  
  ## 0 answers
  
  