How to add a surcharge based on the delivery of state? - php

Per http://docs.woothemes.com/document/add-a-surcharge-to-cart-and-checkout-uses-fees-api/
add_action( 'woocommerce_cart_calculate_fees','woocommerce_custom_surcharge' );
function woocommerce_custom_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$county = array('ME');
$percentage = 0.01;
if ( in_array( $woocommerce->customer->get_shipping_state(), $state ) ) :
$surcharge = ( $woocommerce->cart->cart_contents_total + $woocommerce->cart->shipping_total ) * $percentage;
$woocommerce->cart->add_fee( 'Surcharge', $surcharge, true, 'standard' );
endif;
}
How do I add more than one state? So if the person is in OH or FL - I'd like to add a rate for each state.

Write some new code for it...I'll do it for you this time:
add_action( 'woocommerce_cart_calculate_fees','woocommerce_custom_surcharge' );
function woocommerce_custom_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
$state = $woocommerce->customer->get_shipping_state();
if ( $state == 'ME' ) {
$surcharge = ( $woocommerce->cart->cart_contents_total + $woocommerce->cart->shipping_total ) * 0.01;
$woocommerce->cart->add_fee( 'Surcharge', $surcharge, true, 'standard' );
} elseif ( $state == 'OH' ) {
// Do something else
} elseif ( in_array( $state, array('FL', 'CA') ) ) {
// Even something else
}
}

add_action( 'woocommerce_cart_calculate_fees','xa_custom_surcharge' );
function xa_custom_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$state = array('MH');
$surcharge = 10;
if ( in_array( WC()->customer->shipping_state, $state ) ) {
$woocommerce->cart->add_fee( 'Additional Charge', $surcharge, true, '' );
}
}
Try this simple snippet. Got from here

Related

Disabling payment methods based on custom shipping methods in WooCommerce

In WooCommerce shop I have 2 delivery method based on Parcel locker. The first is payable in advance, while the second is cash on delivery.
The idea is:
easypack_parcel_machines -> only Pay in advance
easypack_parcel_machines_cod -> only Cash on delivery
My code below. After applying in both cases, I only have Pay in advance. What's wrong?
add_filter( 'woocommerce_available_payment_gateways', 'gateway_disable_shipping_meth' );
function gateway_disable_shipping_meth( $available_gateways ) {
if ( ! is_admin() ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = $chosen_methods[0];
if ( isset( $available_gateways['bacs'] ) && 0 === strpos( $chosen_shipping, 'easypack_parcel_machines' ) ) {
unset( $available_gateways['cod'] );
}
elseif ( isset( $available_gateways['cod'] ) && 0 === strpos( $chosen_shipping, 'easypack_parcel_machines_cod' ) ) {
unset( $available_gateways['bacs'] );
}
}
return $available_gateways;
}
As both shipping methods start with the same slug, you should simply need to invert them in your if / elseif statement as follows (also there are some other mistake):
add_filter( 'woocommerce_available_payment_gateways', 'gateway_disable_shipping_meth' );
function gateway_disable_shipping_meth( $available_gateways ) {
if ( ! is_admin() ) {
$chosen_shipping = WC()->session->get( 'chosen_shipping_methods' )[0];
if ( isset( $available_gateways['bacs'] ) && 0 === strpos( $chosen_shipping, 'easypack_parcel_machines_cod' ) ) {
unset( $available_gateways['bacs'] );
}
elseif ( isset( $available_gateways['cod'] ) && 0 === strpos( $chosen_shipping, 'easypack_parcel_machines' ) ) {
unset( $available_gateways['cod'] );
}
}
return $available_gateways;
}
or also this way too:
add_filter( 'woocommerce_available_payment_gateways', 'gateway_disable_shipping_meth' );
function gateway_disable_shipping_meth( $available_gateways ) {
if ( ! is_admin() ) {
$chosen_shipping = WC()->session->get( 'chosen_shipping_methods' )[0];
if ( 0 === strpos( $chosen_shipping, 'easypack_parcel_machines' ) ) {
if ( false !== strpos( $chosen_shipping, 'cod' ) && isset( $available_gateways['bacs'] ) ) {
unset( $available_gateways['bacs'] );
} elseif ( isset( $available_gateways['cod'] ) ) {
unset( $available_gateways['cod'] );
}
}
}
return $available_gateways;
}
It should work.

How to Disable Multiple Payment Gateways For Specific Shipping Method [duplicate]

This question already has answers here:
Show hide payment methods based on selected shipping method in Woocommerce
(2 answers)
Hide payment methods based on selected shipping method in WooCommerce [duplicate]
(1 answer)
Closed 2 years ago.
I created this code on WooCommerce to disable multiple payment gateways (cardgatecreditcard, cardgategiropay, cardgateideal and cardgatesofortbanking) for two different shipping methods (request_shipping_quote and flat_rate). But how do I simplify it?
// Disable Payment Gateway For Specific Shipping Method
add_filter( 'woocommerce_available_payment_gateways', 'bbloomer_gateway_disable_shipping_326' );
function bbloomer_gateway_disable_shipping_326( $available_gateways ) {
if ( ! is_admin() ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = $chosen_methods[0];
if ( isset( $available_gateways['cardgatecreditcard'] ) && 0 === strpos( $chosen_shipping, 'flat_rate' ) ) {
unset( $available_gateways['cardgatecreditcard'] );
}
if ( isset( $available_gateways['cardgategiropay'] ) && 0 === strpos( $chosen_shipping, 'flat_rate' ) ) {
unset( $available_gateways['cardgategiropay'] );
}
if ( isset( $available_gateways['cardgateideal'] ) && 0 === strpos( $chosen_shipping, 'flat_rate' ) ) {
unset( $available_gateways['cardgateideal'] );
}
if ( isset( $available_gateways['cardgatesofortbanking'] ) && 0 === strpos( $chosen_shipping, 'flat_rate' ) ) {
unset( $available_gateways['cardgatesofortbanking'] );
}
if ( isset( $available_gateways['cardgatecreditcard'] ) && 0 === strpos( $chosen_shipping, 'request_shipping_quote' ) ) {
unset( $available_gateways['cardgatecreditcard'] );
}
if ( isset( $available_gateways['cardgategiropay'] ) && 0 === strpos( $chosen_shipping, 'request_shipping_quote' ) ) {
unset( $available_gateways['cardgategiropay'] );
}
if ( isset( $available_gateways['cardgateideal'] ) && 0 === strpos( $chosen_shipping, 'request_shipping_quote' ) ) {
unset( $available_gateways['cardgateideal'] );
}
if ( isset( $available_gateways['cardgatesofortbanking'] ) && 0 === strpos( $chosen_shipping, 'request_shipping_quote' ) ) {
unset( $available_gateways['cardgatesofortbanking'] );
}
}
return $available_gateways;
}
Try the following:
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
$gateways_to_disable = array( 'cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking' );
$shipping_methods = array( 'flat_rate', 'request_shipping_quote' );
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $id => $gateway ) {
if ( in_array( $id, $gateways_to_disable ) ) {
unset( $available_gateways[$id] );
}
}
}
return $available_gateways;
}

error post contact form 7 when use the woocommerce filter 'woocommerce_available_payment_gateways'

I use this code snippet to below in order to disable Payment Gateway For Specific Shipping Method.
Snippet found here : https://businessbloomer.com/woocommerce-disable-payment-gateway-for-specific-shipping-method/
function bbloomer_gateway_disable_shipping_30( $available_gateways ) {
global $woocommerce;
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = $chosen_methods[0];
if ( isset( $available_gateways['cod'] ) && 0 === strpos( $chosen_shipping, 'local_pickup' ) ) {
unset( $available_gateways['cod'] );
}
if ( isset( $available_gateways['paypal'] ) && 0 === strpos( $chosen_shipping, 'local_pickup' ) ) {
unset( $available_gateways['paypal'] );
}
if ( isset( $available_gateways['stripe'] ) && 0 === strpos( $chosen_shipping, 'local_pickup' ) ) {
unset( $available_gateways['stripe'] );
}
return $available_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'bbloomer_gateway_disable_shipping_30' );
This code triggers a 500 error with contact form 7
https://moebella24.ninapresotto.com/wp-json/contact-form-7/v1/contact-forms/57004/feedback 500

Automatic discount for paying customers in WooCommerce

I want to apply a discount coupon automatically to everyone who has made at least one purchase on my website. This is the code that I tried but I get a fatal error on the page ...
function has_bought( $customer_email ){
$orders = get_posts(array(
'numberposts' => -1,
'post_type' => 'shop_order',
'post_status' => array('wc-completed'),
) );
$email_array = array();
foreach($orders as $order) {
$order_obj = wc_get_order($order->ID);
$order_obj_data = $order_obj->get_data();
array_push($email_array, $order_obj_data['billing']['email']);
}
if (in_array($customer_email, $email_array)) {
return true;
} else {
return false;
}
}
add_action( 'woocommerce_before_cart', 'apply_matched_coupons' );
function apply_matched_coupons() {
global $woocommerce;
$coupon_code = '10fidelity'; // coupon code
if ( $woocommerce->cart->has_discount( $coupon_code ) ) return;
if ( has bought() {
$woocommerce->cart->add_discount( $coupon_code );
$woocommerce->show_messages();
}
}
Your actual code is heavy and outdated… Instead try the following much more lighter and efficient way, that use the WC_Customer is_paying_customer property:
add_action( 'woocommerce_before_calculate_totals', 'enable_customer_fidelity_discount', 10, 1 );
function enable_customer_fidelity_discount( $cart ) {
if ( ! ( is_cart() || is_checkout() ) )
return;
if ( ( is_admin() && ! defined( 'DOING_AJAX' ) ) || ! is_user_logged_in() )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// The discount coupon code below
$coupon_code = '10fidelity';
if( ! in_array( $coupon_code, $cart->get_applied_coupons() ) && WC()->customer->get_is_paying_customer() ) {
$cart->apply_coupon( $coupon_code );
} elseif( in_array( $coupon_code, $cart->get_applied_coupons() ) && ! WC()->customer->get_is_paying_customer() ) {
$cart->remove_coupon( $coupon_code );
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works
Or using this improved and light function that checks if customer has already made an order:
function has_bought( $user_id = 0 ) {
global $wpdb;
$customer_id = $user_id == 0 ? get_current_user_id() : $user_id;
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$results = $wpdb->get_col( "
SELECT p.ID FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
" );
// Count number of orders and return a boolean value depending if higher than 0
return count( $results ) > 0 ? true : false;
}
add_action( 'woocommerce_before_calculate_totals', 'enable_customer_fidelity_discount', 10, 1 );
function enable_customer_fidelity_discount( $cart ) {
if ( ! ( is_cart() || is_checkout() ) )
return;
if ( ( is_admin() && ! defined( 'DOING_AJAX' ) ) || ! is_user_logged_in() )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// The discount coupon code below
$coupon_code = 'summer';
if( ! in_array( $coupon_code, $cart->get_applied_coupons() ) && has_bought() ) {
$cart->apply_coupon( $coupon_code );
} elseif( in_array( $coupon_code, $cart->get_applied_coupons() ) && ! has_bought() ) {
$cart->remove_coupon( $coupon_code );
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related:
Checking if customer has already bought something in WooCommerce
Auto apply coupon only one time per user based on total spent in WooCommerce

Adding multiple items to WooCommerce cart at once

I have 3 IDs of the different items that I want to add to my shopping cart.
I could use https://url.com/shop/cart/?add-to-cart=3001 but when I want to add 3 items I can't do it. Is there any function/script I can add to add this ability to my shopping website?
I tried to add an & after the add-to-cart and tried to add a new value but GETs get overridden right?:
https://url.com/shop/cart/?add-to-cart=3001&add-to-cart=2002&add-to-cart=1001
I found the answer!
Simply add the following script to your theme's functions.php:
function woocommerce_maybe_add_multiple_products_to_cart() {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action();
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->product_type, $adding_to_cart );
/*
* Sorry.. if you want non-simple products, you're on your own.
*
* Related: WooCommerce has set the following methods as private:
* WC_Form_Handler::add_to_cart_handler_variable(),
* WC_Form_Handler::add_to_cart_handler_grouped(),
* WC_Form_Handler::add_to_cart_handler_simple()
*
* Why you gotta be like that WooCommerce?
*/
if ( 'simple' !== $add_to_cart_handler ) {
continue;
}
// For now, quantity applies to all products.. This could be changed easily enough, but I didn't need this feature.
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
And then you can simply use http://shop.com/shop/cart/?add-to-cart=3001,3282 to add multiple items at once. Put a comma between different IDs.
Thanks to dsgnwrks for the solution.
In case anyone else comes by looking for this function like I did. I updated the top comment's code allowing it to work with woocommerce 3.5.3
// adds support for multi add to cart for 3rd party cart plugin
function woocommerce_maybe_add_multiple_products_to_cart() {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action();
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
// only works for simple atm
if ( $adding_to_cart->is_type( 'simple' ) ) {
// quantity applies to all products atm
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
}
}
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
Thanks to #thatgerhard and dsgnwrks
I found a solution for support variations:
function woocommerce_maybe_add_multiple_products_to_cart() {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action();
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
if ( $adding_to_cart->is_type( 'simple' ) ) {
// quantity applies to all products atm
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
} else {
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) );
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // WPCS: sanitization ok.
$missing_attributes = array();
$variations = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
}
// Gather posted attributes.
$posted_attributes = array();
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $_REQUEST[ $attribute_key ] ) ) {
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $_REQUEST[ $attribute_key ] ) );
} else {
$value = html_entity_decode( wc_clean( wp_unslash( $_REQUEST[ $attribute_key ] ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // WPCS: sanitization ok.
}
$posted_attributes[ $attribute_key ] = $value;
}
}
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, $posted_attributes );
}
// Do we have a variation ID?
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options…', 'woocommerce' ) );
}
// Check the data we have is valid.
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
// Get valid value from variation data.
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
$valid_value = isset( $variation_data[ $attribute_key ] ) ? $variation_data[ $attribute_key ]: '';
/**
* If the attribute value was posted, check if it's valid.
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
if ( isset( $posted_attributes[ $attribute_key ] ) ) {
$value = $posted_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
$variations[ $attribute_key ] = $value;
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs() ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$variations[ $attribute_key ] = $value;
} else {
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
}
} elseif ( '' === $valid_value ) {
$missing_attributes[] = wc_attribute_label( $attribute['name'] );
}
}
if ( ! empty( $missing_attributes ) ) {
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
}
}
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
Here I had forced $_REQUEST[ 'add-to-cart' ] to take the intended product id. Overwriting global variable doesn't sound good approach but it keeps DRY approach and also make all native function available to user.
add_action( 'wp_loaded', 'add_multiple_to_cart_action', 20 );
function add_multiple_to_cart_action() {
if ( ! isset( $_REQUEST['multiple-item-to-cart'] ) || false === strpos( wp_unslash( $_REQUEST['multiple-item-to-cart'] ), '|' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return;
}
wc_nocache_headers();
$product_ids = apply_filters( 'woocommerce_add_to_cart_product_id', wp_unslash( $_REQUEST['multiple-item-to-cart'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
$product_ids = explode( '|', $product_ids );
if( ! is_array( $product_ids ) ) return;
$product_ids = array_map( 'absint', $product_ids );
$was_added_to_cart = false;
$last_product_id = end($product_ids);
//stop re-direction
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
foreach ($product_ids as $index => $product_id ) {
$product_id = absint( $product_id );
if( empty( $product_id ) ) continue;
$_REQUEST['add-to-cart'] = $product_id;
if( $product_id === $last_product_id ) {
add_filter( 'option_woocommerce_cart_redirect_after_add', function() {
return 'yes';
} );
} else {
add_filter( 'option_woocommerce_cart_redirect_after_add', function() {
return 'no';
} );
}
WC_Form_Handler::add_to_cart_action();
}
}
Example:
https://your-domain/cart?multiple-item-to-cart=68|69
Thanks
Same answer as others, with support for multiple quantities.
Example url: http://store.amitbend.com/cart/?add-to-cart=188,187,189&quantities=3,2,1
function woocommerce_maybe_add_multiple_products_to_cart() {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$quantities = explode( ',', $_REQUEST['quantities'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action();
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->product_type, $adding_to_cart );
/*
* Sorry.. if you want non-simple products, you're on your own.
*
* Related: WooCommerce has set the following methods as private:
* WC_Form_Handler::add_to_cart_handler_variable(),
* WC_Form_Handler::add_to_cart_handler_grouped(),
* WC_Form_Handler::add_to_cart_handler_simple()
*
* Why you gotta be like that WooCommerce?
*/
if ( 'simple' !== $add_to_cart_handler ) {
continue;
}
// $_REQUEST['quantity'] = ! empty( $id_and_quantity[1] ) ? absint( $id_and_quantity[1] ) : 1;
$_REQUEST['quantity'] = ! empty( $quantities[$number] ) ? absint( $quantities[$number] ) : 1;
$quantity = empty( $quantities[$number - 1] ) ? 1 : wc_stock_amount( $quantities[$number - 1] );
// $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
// remove "added to cart" notice
add_filter( 'wc_add_to_cart_message_html', '__return_false' );
The correct syntax is
add-to-cart=product1id:quantity,product2id:quantity
This is the code
https://www.webroomtech.com/woocommerce-add-multiple-products-to-cart-via-url/
They don't show this syntax for quantities on the tutorial, I found it looking at the code.
WooCommerce 4.0, an easier way to do this is to use the template_redirect hook which fires early off in the process. In theory you could decode a URL and add multiple items to cart or POST a form and process the POST. The simplest form is:
add_action( 'template_redirect', function() {
if( ! isset( $_POST['products'] ) ) {
return;
}
$products = array_filter( $_POST['products'] );
foreach( $products as $product_id => $quantity ) {
WC()->cart->add_to_cart( $product_id, $quantity );
}
} );
If you don't want to code anything, you can create a grouped product with all the products you want to add to the cart added as Linked Products.
Then the url will be as follow:
https://url.com/shop/cart/?add-to-cart=3001&quantity[2002]=1&quantity[1001]=1
3001 = Grouped Product id
2002 = Linked Product1 id
1001 = Linked Product2 id
Don't forget to specify quantities.
Also, you can change the catalog visibility to "hidden" so that grouped product doesn't appear in your store.
I took a different approach to this. Instead of killing the WC_Form_Handler, I use it! I have it run for each product. This seems simpler to me.
You can use this alone, or also with the regular add-to-cart.
http://example.com?add-more-to-cart=1000,10001,10002
http://example.com?add-to-cart=1000&add-more-to-cart=10001,10002
class add_more_to_cart {
private $prevent_redirect = false; //used to prevent WC from redirecting if we have more to process
function __construct() {
if ( ! isset( $_REQUEST[ 'add-more-to-cart' ] ) ) return; //don't load if we don't have to
$this->prevent_redirect = 'no'; //prevent WC from redirecting so we can process additional items
add_action( 'wp_loaded', [ $this, 'add_more_to_cart' ], 21 ); //fire after WC does, so we just process extra ones
add_action( 'pre_option_woocommerce_cart_redirect_after_add', [ $this, 'intercept_option' ], 9000 ); //intercept the WC option to force no redirect
}
function intercept_option() {
return $this->prevent_redirect;
}
function add_more_to_cart() {
$product_ids = explode( ',', $_REQUEST['add-more-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) $this->prevent_redirect = false; //this is the last one, so let WC redirect if it wants to.
$_REQUEST['add-to-cart'] = $product_id; //set the next product id
WC_Form_Handler::add_to_cart_action(); //let WC run its own code
}
}
}
new add_more_to_cart;
support variations and quantities using add-to-cart and quantities
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$quantities = explode( ',', $_REQUEST['quantities'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $product_id ) {
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action();
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
if ( $adding_to_cart->is_type( 'simple' ) ) {
$_REQUEST['quantity'] = ! empty( $quantities[$number] ) ? absint( $quantities[$number] ) : 1;
$quantity = empty( $quantities[$number - 1] ) ? 1 : wc_stock_amount( $quantities[$number - 1] );
// quantity applies to all products atm
//$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
} else {
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) );
$quantity = empty( $quantities[$number - 1] ) ? 1 : wc_stock_amount( wp_unslash( $quantities[$number - 1] ) ); // WPCS: sanitization ok.
$missing_attributes = array();
$variations = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
}
// Gather posted attributes.
$posted_attributes = array();
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $_REQUEST[ $attribute_key ] ) ) {
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $_REQUEST[ $attribute_key ] ) );
} else {
$value = html_entity_decode( wc_clean( wp_unslash( $_REQUEST[ $attribute_key ] ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // WPCS: sanitization ok.
}
$posted_attributes[ $attribute_key ] = $value;
}
}
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, $posted_attributes );
}
// Do we have a variation ID?
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options…', 'woocommerce' ) );
}
// Check the data we have is valid.
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
// Get valid value from variation data.
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
$valid_value = isset( $variation_data[ $attribute_key ] ) ? $variation_data[ $attribute_key ]: '';
/**
* If the attribute value was posted, check if it's valid.
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
if ( isset( $posted_attributes[ $attribute_key ] ) ) {
$value = $posted_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
$variations[ $attribute_key ] = $value;
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs() ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$variations[ $attribute_key ] = $value;
} else {
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
}
} elseif ( '' === $valid_value ) {
$missing_attributes[] = wc_attribute_label( $attribute['name'] );
}
}
if ( ! empty( $missing_attributes ) ) {
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
}
}
I've tried these solutions but nothing worked since init ran a few times and the products were adding a few times as well so the quantities were off.
In addition, I wanted to have the ability to add a coupon from the URL as well...
/*
* Init function
* 1. Apply coupon code from URL
* 2. Add to cart: Multiple products add via URL
*/
function nd_template_redirect() {
if( !isset( WC()->session ) || ( isset( WC()->session ) && !WC()->session->has_session() ) ) {
WC()->session->set_customer_session_cookie( true );
}
$product_ids = array();
$quantities = array();
if( isset( $_GET['add-to-cart'] ) ) {
if( false !== strpos( $_GET['add-to-cart'], ',' ) ) {
$product_ids = explode( ',', $_GET['add-to-cart'] );
}
}
if( isset( $_GET['quantity'] ) ) {
if( false !== strpos( $_GET['quantity'], ',' ) ) {
$quantities = explode( ',', $_GET['quantity'] );
}
}
if( !empty( $product_ids ) ) {
$products = array();
for( $i = 0; $i < count( $product_ids ); $i++ ) {
if( isset( $product_ids[ $i ] ) ) {
$products[ $product_ids[ $i ] ] = isset( $quantities[ $i ] ) ? $quantities[ $i ] : 1;
}
}
if( !empty( $products ) ) {
foreach( $products as $key => $value ) {
WC()->cart->add_to_cart( $key, $value );
}
}
}
if( isset( $_GET['coupon_code'] ) && empty( WC()->session->get( 'added_coupon_code' ) ) ) {
$coupon_code = esc_attr( $_GET['coupon_code'] );
if( !empty( $coupon_code ) && !WC()->cart->has_discount( $coupon_code ) ) {
WC()->cart->add_discount( $coupon_code );
WC()->session->set( 'added_coupon_code', true );
}
}
}
add_action( 'template_redirect', 'nd_template_redirect' );
/*
* Clear session variables on thank you page.
*/
function nd_woocommerce_thankyou( $order_id ) {
WC()->session->__unset( 'added_coupon_code' );
WC()->session->__unset( 'added_products_to_cart' );
}
add_action( 'woocommerce_thankyou', 'nd_woocommerce_thankyou' );
Example usage:
https://example.com/cart/?add-to-cart=19737,19713&quantity=2,1&coupon_code=TEST
A few notes:
This works with or without quantity and coupon code parameters, if you choose to omit them the quantity of the products that will be added to the cart will be 1.
The product ids have to be separated using commas without spaces.
Match the product ids with the quantity in the correct order, in this example 2 units of product 19737 will be added to the cart.
This solution works only for URLs that send the user to the cart page.
Considering correct syntax that would be the following to create the correct url.
www.website.com/cart/add-to-cart=[product1id]:[quantity],[product2id]:[quantity]
As I am extracting the product ID from a shortcode, and the quantity from another shortcode, how do I tell the system that if it is empty, don't show it.
The formula works fine, maybe because I use shortcode it fails and returns 0 which becomes 1, the code works for all quantity fields that have a value inserted, the url if it sees that it has no value, it returns 0 and adds the product as quantity 1.
function webroom_add_multiple_products_to_cart( $url = false ) {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $id_and_quantity ) {
// Check for quantities defined in curie notation (<product_id>:<product_quantity>)
$id_and_quantity = explode( ':', $id_and_quantity );
$product_id = $id_and_quantity[0];
$_REQUEST['quantity'] = ! empty( $id_and_quantity[1] ) ? absint( $id_and_quantity[1] ) : 1;
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action( $url );
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_variable', $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_grouped', $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ){
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
} else {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_simple', $product_id );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'webroom_add_multiple_products_to_cart', 15 );
/**
* Invoke class private method
*
* #since 0.1.0
*
* #param string $class_name
* #param string $methodName
*
* #return mixed
*/
function woo_hack_invoke_private_method( $class_name, $methodName ) {
if ( version_compare( phpversion(), '5.3', '<' ) ) {
throw new Exception( 'PHP version does not support ReflectionClass::setAccessible()', __LINE__ );
}
$args = func_get_args();
unset( $args[0], $args[1] );
$reflection = new ReflectionClass( $class_name );
$method = $reflection->getMethod( $methodName );
$method->setAccessible( true );
//$args = array_merge( array( $class_name ), $args );
$args = array_merge( array( $reflection ), $args );
return call_user_func_array( array( $method, 'invoke' ), $args );
}
Here I found the code and then I discovered this chat where they were talking about the same thing.

Categories