0

I'm adding a custom fees to WC with WC()->cart->add_fee() method.

My problem is that I'd like to add metadata to that fee item too. Preferably same time I'm adding the actual fee.

Apparently the WC_Order_Item_Fee Object is generated in the order creation only, so there seems to be no way to add FeeItem-specific metadata to custom fees.

Of course I could save this meta to session, but because add_fee doesn't return any identifier I have no idea which custom fee is actually which.

Any ideas how to solve this issue?

This is the code I use to add Fees:

add_filter('woocommerce_cart_calculate_fees', function (){
    foreach( FeeChecker::getFees() as $fee )
    {
        $cart->add_fee("Added fee: ". $fee, 10 , true, $tax_class);
    }
}
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
sarte
  • 315
  • 1
  • 3
  • 11

3 Answers3

4

Note: In your code the $cart argument is missing from the hooked function and it's an action hook, but not a filter hook.

The WC_Cart method add_fee() doesn't allow to add custom meta data, so you will need to add it before on add_to_cart event or in WC_Session.

You can add custom meta data to WC_Order_Item_Fee when order is submitted using the following code example (using WC_Session to set and get the custom meta data in here):

// Add a custom cart fee
add_action( 'woocommerce_cart_calculate_fees', 'adding_cart_fees', 10, 1 );
function adding_cart_fees( $cart ){
    $cart->add_fee(__("Added fee"), 10, true, '');
}

// Set Fee custom meta data in WC_Session
add_action( 'woocommerce_calculate_totals', 'calculate_totals_for_fees_meta_data', 10, 1 );
function calculate_totals_for_fees_meta_data( $cart ){
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    $fees_meta = WC()->session->get('fees_meta');
    $update    = false;

    // Loop through applied fees
    foreach( $cart->get_fees() as $fee_key => $fee ) {
        // Set the fee in the fee custom meta data array
        if( ! isset($fees_meta[$fee_key]) ){
            $fees_meta[$fee_key] = 'some value';
            $update = true;
        }
    }
    // If any fee meta data doesn't exist yet, we update the WC_Session custom meta data array
    if ( $update ) {
        WC()->session->set('fees_meta', $fees_meta);
    }
}

// Save fee custom meta data to WC_Order_Item_Fee.
add_action( 'woocommerce_checkout_create_order_fee_item', 'save_custom_met_data_to_fee_order_items', 10, 4 );
function save_custom_met_data_to_fee_order_items( $item, $fee_key, $fee, $order ) {
    // Get fee meta data from WC_Session
    $fees_meta = WC()->session->get('fees_meta');
    // If fee custom meta data exist, save it to fee order item
    if ( isset($fees_meta[$fee_key]) ) {
        $item->update_meta_data( 'custom_key', $fees_meta[$fee_key] );
    }
}

// Remove Fee meta data from WC_Session.
add_action( 'woocommerce_checkout_create_order', 'remove_fee_custom_met_data_from_wc_session', 10, 2 );
function remove_fee_custom_met_data_from_wc_session( $order, $data ) {
    $fees_meta = WC()->session->__unset('fees_meta');
}

Code goes in function.php file of your active child theme (or active theme). Tested and works.

The custom meta data is saved to WC_Order_Item_Fee and you can get it using something like:

// Get an instance of the WC_Order Object (if needed)
$order = wc_get_order( $order_id );

// loop through fee order items
foreach ( $order->get_items('fee') as $fee_key => $item ) {
    // Get the fee custom meta data
    $fee_custom_meta = $item->get_meta('custom_key');

    if ( $fee_custom_meta ) {
        // Display the custom meta data value
        echo '<p>' . $fee_custom_meta . '</p>';
    }
}
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • This won't actually solve the issue. Main use case here is to create additional fee per delivery package. And every package will have a custom fee description ( hence the for loop in my example ) The basic issue still remains. I need a way to reference the created fee so I know which metadata belongs to which fee. I know it when I create the fee, but because fee creation doesn't generate any kind of key it can't be referenced in woocommerce_checkout_create_order_fee_item-action. With this solution I would need to some how know which Fee is which and that is the original issue. – sarte Feb 05 '19 at 10:57
  • @sarte Your question code and explanations is very poor, so nobody should be able to guess the real way to make it work for your case… My answer works as it's and give the way, and your actual new code is mainly based on my answer code... – LoicTheAztec Feb 06 '19 at 13:41
0

A somewhat hacky way is to use the ID field of the fee item to link to data stored somewhere else.

$args = array(
    'id'        => $data_id,
    'name'      => $name,
    'amount'    => (float) $amount,
    'taxable'   => false,
    'tax_class' => '',
);
$cart->fees_api()->add_fee( $args );

Later you can use the hook woocommerce_checkout_create_order_fee_item to fetch the data and populate the order item fee. In my case:

add_action( 'woocommerce_checkout_create_order_fee_item', array( $this, 'create_order_fee_item' ), 20, 4 );
public function create_order_fee_item( $item, $fee_key, $fee, $order ) {
    $item->add_meta_data( 'product_id', $fee_key, true );
}
Leukipp
  • 550
  • 2
  • 9
  • 25
-3

It seems I overlooked WC_Cart->fees_api

In there I have method that actually returns the created Fee so I know the exact Fee in woocommerce_checkout_create_order_fee_item action

Edit: This code was originally mostly done by LoicTheAztec. I edited it to fit my particular use case and posted it as solution. Unfortunatelly he deleted his post which could have been beneficial to others.

// Add cod fee
add_action('woocommerce_cart_calculate_fees', function ( $cart ){
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    //Make sure it's the right payment method
    if( WC()->session->chosen_payment_method == "cod"){

        $tax_class = '';
        $amount = 5;

        $session_data = [];
        foreach(  $cart->get_shipping_packages() as $package)
        {
            $fee = $cart->fees_api()->add_fee(

                array(
                    'name'      => "Additional cost: ".$package['custom_data'],
                    'amount'    => (float) $amount,
                    'taxable'   => true,
                    'tax_class' => $tax_class,
                )
            );

            $session_data [ $fee->id ] = $package['custom_data'];
        }

        WC()->session->set('COD_fee_meta', $session_data);

    }

}, 10, 2);

// Save fee custom meta data to WC_Order_Item_Fee.
add_action( 'woocommerce_checkout_create_order_fee_item', function ( $item, $fee_key, $fee, $order ) {
    // Get fee meta data from WC_Session
    $fees_meta = WC()->session->get('COD_fee_meta');

    // If fee custom meta data exist, save it to fee order item
    if ( isset($fees_meta[$fee_key]) ) {
        $item->update_meta_data( '_custom_data', $fees_meta[$fee_key] );
    }

}, 10, 4 );

// Remove Fee meta data from WC_Session.
add_action( 'woocommerce_checkout_create_order', function ( $order, $data ) {
    WC()->session->__unset('COD_fee_meta');
}, 10, 2 );
sarte
  • 315
  • 1
  • 3
  • 11
  • Thanks for LoicTheAztec for pointing me to correct direction. – sarte Feb 05 '19 at 12:04
  • Terribly sorry about this one. I didn't mean to upset you. The original code you provided didn't actually solve the problem I had as I explained in the comment I left to your solution. Yes I borrowed some pats of your code but there was a clear difference between them. But I'll edit my solution and add credit for you from the session part. – sarte Feb 06 '19 at 13:21
  • 1
    All my code is always tested and works… You have provided in your question a very poor generic code and explanations (you didn't told anything about COD). Your new working code is mainly based on my answer code and should be an edit in your question as without my answer, you could not make it yourself. So my answer should be accepted as it works as it is, and gives you the way to make it work for your case and it's useful to the community. – LoicTheAztec Feb 06 '19 at 13:29