I've built a custom WooCommerce cart where you can change quantities and / or remove items from your cart with AJAX, but it's buggy and I think it's got something to do with WooCommerce and the validity of WordPress nonces.
The problem:
It works when there's a product in your cart and you refreshed the page at least once - after adding the product to your cart.
It doesn't work when it's your first visit, you add a product in your cart and you try to edit the quantities of the product, or try to delete it.
See for yourself at https://staging.noten.nl/noten/ - check on your smartphone please. Add a product to the cart, click on it and change the values (250g more / 250g less / delete product)
PHP - Nonce creation
/*
** Theme scripts
*/
function scripts() {
// Enqueue scripts
wp_enqueue_script( 'noten-nl/js', Assets\asset_path('scripts/main.js?v=' . VERSION), ['jquery-core', 'wp-util'], null, true );
// Localize script
wp_localize_script( 'noten-nl/js', 'shop', array(
'url' => admin_url( 'admin-ajax.php' ),
'cart_more' => wp_create_nonce( 'cart-more-nonce' ),
'cart_less' => wp_create_nonce( 'cart-less-nonce' ),
'cart_delete' => wp_create_nonce( 'cart-delete-nonce' )
));
}
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\\scripts', 999 );
Javascript (calls cart_more PHP function below)
/*
** Edit items in cart
*/
function cartAction(event) {
// Log
console.log('cartAction');
// Variables
var action = $(event.currentTarget).attr('data-action'),
product = $('.cart-products-scroll .row.active');
// Load
product.children('.cart-row-item-loading').show();
// AJAX
wp.ajax.send('cart_' + action, {
data: {
nonce: shop['cart_' + action],
id: product.attr('data-product-id'),
quantity: product.attr('data-product-quantity'),
key: product.attr('data-product-cart-item-key')
},
success: function (fragments) {
// Replace fragments
$.each(fragments, function (key, value) {
$(key).replaceWith(value);
});
},
error: function (response) {
console.log(response);
}
});
}
PHP
function cart_more() {
// Log
write_log( 'cart_more()' );
// Variables
$nonce = isset( $_POST['nonce'] ) ? $_POST['nonce'] : '';
$product_id = isset( $_POST['id'] ) ? $_POST['id'] : '';
$product_quantity = isset( $_POST['quantity'] ) ? $_POST['quantity'] : '';
// Check data
if ( wp_verify_nonce( $nonce, 'cart-more-nonce' ) && ! empty( $product_id ) && ! empty( $product_quantity ) ) {
/*
** Removed for readability
*/
// Send success
wp_send_json_success( $fragments );
} else {
// Send error
wp_send_json_error( ':\'(' );
}
}
add_action( 'wp_ajax_nopriv_cart_more', __NAMESPACE__ . '\\cart_more' );
add_action( 'wp_ajax_cart_more', __NAMESPACE__ . '\\cart_more' );
Question
Why does nonce verification only succeed after adding something to my cart?
In order to make product pages cacheable WooCommerce sessions are not created until a cart is created.1,2
WooCommerce overrides one of the nonce parameters with a value which changes based on whether or not a WooCommerce session has been created.3,4
When you create the nonce for a new user with no cart and no session the nonce is calculated with one set of inputs. When you check the nonce after an item has been added to the cart the check value is generated with a different set of inputs because the WooCommerce session now exists. This causes a different nonce value to be generated and the nonce check against the old nonce value to fail.
One workaround is to proactively create the WooCommerce session before creating the nonces. Note this could impact how your site is cached.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With