Search code examples
phpwordpresswoocommercemethodscart

Woocommerce temporary cart tax not calculating correctly on 1st request


I am making a custom quick order form plugin for a customer that has difficulty filling in the complex Woocommerce "New Order" form (with all of its Update and Calculate buttons that need to be clicked in a certain succession)...

I have been able to get it to set the customer's shipping and billing addresses, using WC()->customer->set_billing_state() and WC()->customer->set_shipping_state() etc and get the UPS rates for the items, but the Tax calculation always seems to require a 2nd ajax request to work properly.

For example... I enter the billing/shipping addresses, the line items, and hit Calculate Shipping... this action also returns the output of WC()->cart->get_tax_totals() however, it always returns no tax the first time i request it. If i hit "Calculate Shipping" a 2nd time, it shows correct tax. Similarly, if i Change the Shipping address and hit Calculate Shipping, the original tax remains until i hit Calculate Shipping a 2nd time...

How would i go about forcing Woocommerce to calculate tax from the address submitted, and why is it behaving this way?

add_action('wp_ajax_docalcshipping','docalcshipadmin');
function docalcshipadmin(){
    
    //decode the cart data from POST
    $items=$_POST['items'];
    
    //create a new Cart object
    WC()->cart->empty_cart();
    
    if ( WC()->cart->get_cart_contents_count() == 0 ) {

        foreach($items as $n => $item){
            WC()->cart->add_to_cart( $item['item_id'],$item['qty'] );
        }

    }
    
    WC()->customer->set_billing_city($_POST['billing_city']);
    WC()->customer->set_billing_state($_POST['billing_state']);
    WC()->customer->set_billing_postcode($_POST['billing_zipcode']);
    
    WC()->customer->set_shipping_city($_POST['shipping_city']);
    WC()->customer->set_shipping_state($_POST['shipping_state']);
    WC()->customer->set_shipping_postcode($_POST['shipping_zipcode']);
    
    //run shipping calculation
    $shipData=WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages());
        
    $ratesOut='';
    foreach($shipData as $n => $d){
        foreach($d['rates'] as $nn => $dd){
            $ratesOut .= '<p><label><input type="radio" name="shipping_method" value="'.$nn.'" /> '.$dd->get_label().' <b>$'.$dd->get_cost().'</b></label></p>';
        }
    }
    
    $output=array('shipout'=>$ratesOut);

    
    //get TAX calculation
    $taxes=WC()->cart->get_tax_totals();
    
    if(count($taxes) == 0){
        $output['taxout']='NONE';
    }else{
    
        $taxOut=array();
        foreach($taxes as $tax){
            $taxOut[$tax->label]=$tax->amount;
        }
        $output['taxout']=$taxOut;
    }
    
    echo json_encode($output);

    //destroy Cart object
    WC()->cart->empty_cart();
    WC()->session->destroy_session();

    exit;
}

I sort of got around the problem by making 2 AJAX requests and only using the 2nd response, but that seems wasteful.


Solution

  • To avoid wrong calculations, before trying to get cart totals, I think that you need to trigger calculate_totals() method, like:

    // ... your code before
    
    WC()->cart->calculate_totals(); // Trigger cart calculations
    
    $taxes = WC()->cart->get_tax_totals(); // Get TAX calculations
    
    // ... your code after
    

    It should work.


    Note that if ( WC()->cart->get_cart_contents_count() == 0 ) { could be replaced by:

    if ( WC()->cart->is_empty() ) {