craftcms / cms

Build bespoke content experiences with Craft.
https://craftcms.com
Other
3.28k stars 635 forks source link

400 error when trying to ajax posts (add to cart, update quantity and so forth with craft commerce) #3847

Closed 23d1 closed 5 years ago

23d1 commented 5 years ago

Description

Ajax posts return 400 error ("Unable to verify your data submission.") after updating Craft CMS. I'm not 100% sure when this issue occurred, but I believe it's one of the later Craft CMS updates (after 3.1.8). I restored to Craft CMS 3.1.8, which works as it should.

Here's one of many ajax calls that fails (seems like they all fail)—sorry for the spaghetti code;

if (!document.querySelector('form#product-add-cart')) return;
    $('form#product-add-cart').submit(function(e){
        e.preventDefault();
        const purchasable = $(this).find('select[name="purchasableId"]').val();
        const submitter = $(this).find('[type="submit"]');
        const typeVal = $(this).find('select[name="variantType"]').val();
        const colorVal = $(this).find('select[name="variantColor"]').val();
        const type = $(this).find('#variantType .product-variant-title');
        const color = $(this).find('#variantColor .product-variant-title');
        const productTitle = $('#product-info .product-info-title').text();
        const priceVal = $('[data-variant-price-full]').text().replace(/\D/g,'');
        if ($('[data-variant-price-sale]').text().length != 0) {
            const priceVal = $('[data-variant-price-sale]').text().replace(/\D/g,'');
        }
        if (purchasable) {
            submitter.find('span').html('Adding...');
            cart.addClass('loading');
            $.ajax({
                type: "POST",
                dataType: 'json',
                headers: {
                    "X-CSRF-Token" : '{{ craft.app.request.csrfToken }}',
                },
                url: '',
                data: {
                    'action' : 'commerce/cart/update-cart',
                    'purchasableId': purchasable
                },
                success: function(response){
                    var shoppingBag = $('#nav-cart use, #nav-cart-button use');
                    if (response.cart.totalQty <= 0) {
                        shoppingBag.attr('xlink:href', '#shoppingBagEmpty');
                    } else {
                        shoppingBag.attr('xlink:href', '#shoppingBagFull');
                    }
                    $('#nav-cart-bag').html(response.cart.totalQty + ' items');
                    $('#nav-cart-button-number').html(response.cart.totalQty);
                    $('#cart-contents').load(' #cart-contents>*', function(){
                        setCart();
                        setCartQuantity();
                        countryState();
                        addressSelect();
                        paymentForms();
                        cart.addClass('has-items').removeClass('loading');
                        $('#step-cart').addClass('active').addClass('done');
                        cartSticky = document.querySelectorAll('.vs-sticky-cart');
                    });
                    $('#cart-pay span').html('Pay $'+response.cart.totalPrice);
                    submitter.find('span').html('Added');
                    setTimeout(function () {
                        submitter.find('span').html('Add to cart');
                    }, 1000);
                    // Google Analytics Event
                    if (typeof ga == 'function') {
                        ga('send', 'event', {
                            eventCategory: 'Cart',
                            eventAction: 'Add',
                            eventLabel: productTitle+' '+typeVal+' '+colorVal+' (Product ID: '+purchasable+' Price: '+priceVal+' — '+response.cart.number+' )',
                            eventValue: priceVal
                        });
                    }
                }
            });
        } else {
            if (!typeVal) {
                type.css({color: '#ff0000'});
            }
            if (!colorVal) {
                color.css({color: '#ff0000'});
            }
        }
    });

Here's the error in the web.log;

2019-02-14 20:18:19 [-][1][-][error][yii\web\HttpException:400] yii\web\BadRequestHttpException: Unable to verify your data submission. in /Users/a/craft/vendor/yiisoft/yii2/web/Controller.php:166
Stack trace:
#0 /Users/a/craft/vendor/craftcms/cms/src/web/Controller.php(88): yii\web\Controller->beforeAction(Object(yii\base\InlineAction))
#1 /Users/a/craft/vendor/yiisoft/yii2/base/Controller.php(155): craft\web\Controller->beforeAction(Object(yii\base\InlineAction))
#2 /Users/a/craft/vendor/craftcms/cms/src/web/Controller.php(109): yii\base\Controller->runAction('update-cart', Array)
#3 /Users/a/craft/vendor/yiisoft/yii2/base/Module.php(528): craft\web\Controller->runAction('update-cart', Array)
#4 /Users/a/craft/vendor/craftcms/cms/src/web/Application.php(297): yii\base\Module->runAction('commerce/cart/u...', Array)
#5 /Users/a/craft/vendor/craftcms/cms/src/web/Application.php(561): craft\web\Application->runAction('commerce/cart/u...', Array)
#6 /Users/a/craft/vendor/craftcms/cms/src/web/Application.php(281): craft\web\Application->_processActionRequest(Object(craft\web\Request))
#7 /Users/a/craft/vendor/yiisoft/yii2/base/Application.php(386): craft\web\Application->handleRequest(Object(craft\web\Request))
#8 /Users/a/web/index.php(21): yii\base\Application->run()
#9 /Users/a/.composer/vendor/laravel/valet/server.php(158): require('/Users/a/...')
#10 {main}

Additional info

23d1 commented 5 years ago

Update: If I simply change the code somewhat to serialize the form (var data = $(this).serialize();), and set the post data to data: data, it works, but that's not a feasible solution in other parts of the site, like updating amounts of products in the cart by clicking a + and so forth.

Also, defining the CSRF token like so var csrf = $('form#cart-form input[name="CRAFT_CSRF_TOKEN"]').val(); and then passing it as 'CRAFT_CSRF_TOKEN' : csrf, in the data post seems to work as well.

Should I conclude that there is an issue getting the CSRF token using headers: { X-CSRF-Token : '{{ craft.app.request.csrfToken }} }' through JavaScript/AJAX?

brandonkelly commented 5 years ago

Nothing changed recently that should have had any effect on the X-Csrf-Token header. Can you verify that your original code works in 3.1.8 but not 3.1.9?

23d1 commented 5 years ago

Gonna test some stuff when I have a moment, but it seems like it could be another dependency that's linked, as when I simply updated to the latest craft in my test environment, and then downgraded vendor/craftcms/cms back to 3.1.8 the issue persisted. I've since then solved it by grabbing the token from the twig generated html instead of relying on sending the header. I'd also be open to testing a non-jquery xhr solution, but I'm not too well versed with xhr and javascript in general.

brandonkelly commented 5 years ago

as when I simply updated to the latest craft in my test environment, and then downgraded vendor/craftcms/cms back to 3.1.8 the issue persisted

OK yeah, most likely something else is the culprit then. Will close this issue.