TransWikia.com

WooCommerce order again and custom item data for price calculation

Stack Overflow Asked by Elizabeth parodi roman on December 22, 2021

I have some problems trying to add/get custom item data when a re-order is made.

First let me explain a little bit: I am using WooCommerce as a invoice maker mostly, so one of the custom changes I had to make was to add custom percentage discount field in each product (that you can edit in cart page too), so my problem is when a re-order is made, cart items are there but the percentage discount does not affect anymore the prices, and if I try to change the percentage value, all prices are 0 (product price and product total).

Here is the code that I’m using:

// Add a custom field before single add to cart
add_action('woocommerce_before_add_to_cart_button', 'custom_product_price_field', 5);
function custom_product_price_field()
{
    echo '<div class="custom-text text">
    <p>Descuento %:</p>
    <input type="text" id="custom_price" name="custom_price" value="" placeholder="e.g. 10" title="Custom Text" class="custom_price text_custom text">
    </div>';
}

// Get custom field value, calculate new item price, save it as custom cart item data
add_filter('woocommerce_add_cart_item_data', 'add_custom_field_data', 20, 3);
function add_custom_field_data($cart_item_data, $product_id, $variation_id)
{
    $product_id = $variation_id > 0 ? $variation_id : $product_id;

    if (!isset($_POST['custom_price'])) {
        return $cart_item_data;
    }

    $custom_price = (float) sanitize_text_field($_POST['custom_price']);


    if ($custom_price > 40) {
        wc_add_notice(__('El descuento debe ser menor a 40%'), 'error');
        return $cart_item_data;

    }

    $product = wc_get_product($product_id); // The WC_Product Object

    $price = (float) $product->get_price();

    $cart_item_data['base_price'] = $price;
    $cart_item_data['new_price'] = $price * (100 - $custom_price) / 100;
    if($custom_price > 0 || !empty($custom_price))
        $cart_item_data['percentage'] = $custom_price . "%";



    return $cart_item_data;
}

// Set the new calculated cart item price
add_action('woocommerce_before_calculate_totals', 'extra_price_add_custom_price', 20, 1);
function extra_price_add_custom_price($cart)
{
    if (is_admin() && !defined('DOING_AJAX')) {
        return;
    }

    if (did_action('woocommerce_before_calculate_totals') >= 2) {
        return;
    }

    foreach ($cart->get_cart() as $cart_item) {

        if (isset($cart_item['new_price'])) {
            $cart_item['data']->set_price((float) $cart_item['new_price']);
        }
    }
}

// Display cart item custom price details
add_filter('woocommerce_cart_item_price', 'display_cart_items_custom_price_details', 20, 3);
function display_cart_items_custom_price_details($product_price, $cart_item, $cart_item_key)
{


    if (isset($cart_item['base_price'])) {
        $product = $cart_item['data'];
        $product_price = wc_price(wc_get_price_to_display($product, array('price' => $cart_item['base_price'])));
    }

    return $product_price;
}

// Add order item meta.
add_action('woocommerce_add_order_item_meta', 'add_order_item_meta', 10, 3);
function add_order_item_meta($item_id, $cart_item, $cart_item_key)
{
    if (isset($cart_item['percentage'])) {
        wc_add_order_item_meta($item_id, 'percentage', $cart_item['percentage']);
    }
}

This part is for edit the percentage discount in cart page:

//Custom Script Register
function discount_update_cart_scripts()
{
    wp_register_script('discount-cart-script', get_stylesheet_directory_uri() . '/js/discount-cart.js', array('jquery'), time(), true);
    wp_localize_script('discount-cart-script', 'discount_vars', array('ajaxurl' => admin_url('admin-ajax.php')));
    wp_enqueue_script('discount-cart-script');
}
add_action('wp_enqueue_scripts', 'discount_update_cart_scripts');

//Update Discount percentage in cart item 
function discount_update_cart_item()
{
    if (!isset($_POST['wpnonce']) || !wp_verify_nonce($_POST['wpnonce'], 'woocommerce-cart')) {
        wp_send_json(array('status' => 'error', 'message' => 'wp nonce error'));
        exit;
    }

    $cart = WC()->cart->cart_contents;
    $cart_id = $_POST['cart_id'];
    $percentage = $_POST['percentage'];

    if ($percentage > 40) {
        wc_add_notice(__('El descuento debe ser menor a 40%'), 'notice');
        wp_send_json(array('status' => 'error', 'message' => 'percentage error'));
        exit;
    }

    wc_clear_notices();

    $cart_item = $cart[$cart_id];
    $cart_item['new_price'] = $cart_item['base_price'] * (100 - $percentage) / 100;
    $cart_item['percentage'] = $percentage . "%";

    WC()->cart->cart_contents[$cart_id] = $cart_item;
    WC()->cart->set_session();
    wp_send_json(array('status' => 'success'));
    exit;
}
add_action('wp_ajax_discount_update_cart_item', 'discount_update_cart_item');

This is javascript code:

(function ($) {
    function updateDiscountMeta(cart_id) {
        $.ajax({
            type: 'POST',
            url: discount_vars.ajaxurl,
            data: {
                action: 'discount_update_cart_item',
                wpnonce: $('#woocommerce-cart-nonce').val(),
                percentage: $('#discount_cart_' + cart_id).val(),
                cart_id: cart_id
            },
            success: function (response) {}
        })
    } 
    $('.discount-cart-item').on('change keyup paste', function () {
        var cart_id = $(this).data('cart-id');
        updateDiscountMeta(cart_id);
    });

    $( document.body ).on( 'updated_cart_totals', function(){
        $('.discount-cart-item').on('change keyup paste', function () {
            var cart_id = $(this).data('cart-id');
            updateDiscountMeta(cart_id);
        });
    });
})(jQuery);

This is for the re-order part (where I am stuck):

add_filter( 'woocommerce_order_again_cart_item_data', 'order_again_custom', 10, 3 );

function order_again_custom($cart_item_meta, $product, $order){
    //Create an array of all the missing custom field keys that needs to be added in cart item.
    $customfields = [
                    'Titulo1', 
        'percentage',
        'custom_price',
        'price',
        'new_price',
                    ];
    global $woocommerce;
    remove_all_filters( 'woocommerce_add_to_cart_validation' );
    if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
    foreach ( $customfields as $key ){
        if(!empty($product[$key])){
            $cart_item_meta[$key] = $product[$key];
        }
    }
    return $cart_item_meta;
}
function cs_add_order_again_to_my_orders_actions( $actions, $order ) {
    if ( $order->has_status( 'completed' ) ) {
        $actions['order-again'] = array(
            'url'  => wp_nonce_url( add_query_arg( 'order_again', $order->get_id() ) , 'woocommerce-order_again' ),
            'name' => __( 'Order Again', 'woocommerce' )
        );
    }

    return $actions;
}

add_filter( 'woocommerce_my_account_my_orders_actions', 'cs_add_order_again_to_my_orders_actions', 50, 2 );

code for cart page

<?php
/**
 * Cart Page
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/cart/cart.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see     https://docs.woocommerce.com/document/template-structure/
 * @package WooCommerce/Templates
 * @version 3.8.0
 */
defined( 'ABSPATH' ) || exit;
do_action( 'woocommerce_before_cart' ); ?>

<form class="woocommerce-cart-form" action="<?php echo esc_url( wc_get_cart_url() ); ?>" method="post">
    <?php do_action( 'woocommerce_before_cart_table' ); ?>

    <table class="shop_table shop_table_responsive cart woocommerce-cart-form__contents" cellspacing="0">
        <thead>
            <tr>
                <th class="product-remove">&nbsp;</th>
                <th class="product-thumbnail">&nbsp;</th>
                <th class="product-name"><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
                <th class="product-price"><?php esc_html_e( 'Price', 'woocommerce' ); ?></th>
                <th class="product-quantity"><?php esc_html_e( 'Quantity', 'woocommerce' ); ?></th>
                <th class="product-quantity"><?php esc_html_e( 'Descuento', 'woocommerce' ); ?></th>
                <th class="product-subtotal"><?php esc_html_e( 'Total', 'woocommerce' ); ?></th>
            </tr>
        </thead>
        <tbody>
            <?php do_action( 'woocommerce_before_cart_contents' ); ?>

            <?php
            foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
                $_product   = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
                $product_id = apply_filters( 'woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key );
                if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_cart_item_visible', true, $cart_item, $cart_item_key ) ) {
                    $product_permalink = apply_filters( 'woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink( $cart_item ) : '', $cart_item, $cart_item_key );
                    ?>
                    <tr class="woocommerce-cart-form__cart-item <?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ); ?>">

                        <td class="product-remove">
                            <?php
                                echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                                    'woocommerce_cart_item_remove_link',
                                    sprintf(
                                        '<a href="%s" class="remove" aria-label="%s" data-product_id="%s" data-product_sku="%s">&times;</a>',
                                        esc_url( wc_get_cart_remove_url( $cart_item_key ) ),
                                        esc_html__( 'Remove this item', 'woocommerce' ),
                                        esc_attr( $product_id ),
                                        esc_attr( $_product->get_sku() )
                                    ),
                                    $cart_item_key
                                );
                            ?>
                        </td>

                        <td class="product-thumbnail">
                        <?php
                        $thumbnail = apply_filters( 'woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key );
                        if ( ! $product_permalink ) {
                            echo $thumbnail; // PHPCS: XSS ok.
                        } else {
                            printf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $thumbnail ); // PHPCS: XSS ok.
                        }
                        ?>
                        </td>

                        <td class="product-name" data-title="<?php esc_attr_e( 'Product', 'woocommerce' ); ?>">
                        <?php
                        if ( ! $product_permalink ) {
                            echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) . '&nbsp;' );
                        } else {
                            echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $_product->get_name() ), $cart_item, $cart_item_key ) );
                        }
                        do_action( 'woocommerce_after_cart_item_name', $cart_item, $cart_item_key );
                        // Meta data.
                        echo wc_get_formatted_cart_item_data( $cart_item ); // PHPCS: XSS ok.
                        // Backorder notification.
                        if ( $_product->backorders_require_notification() && $_product->is_on_backorder( $cart_item['quantity'] ) ) {
                            echo wp_kses_post( apply_filters( 'woocommerce_cart_item_backorder_notification', '<p class="backorder_notification">' . esc_html__( 'Available on backorder', 'woocommerce' ) . '</p>', $product_id ) );
                        }
                        ?>
                        </td>

                        <td class="product-price" data-title="<?php esc_attr_e( 'Price', 'woocommerce' ); ?>">
                            <?php
                                echo apply_filters( 'woocommerce_cart_item_price', WC()->cart->get_product_price( $_product ), $cart_item, $cart_item_key ); // PHPCS: XSS ok.
                            ?>
                        </td>

                        <td class="product-quantity" data-title="<?php esc_attr_e( 'Quantity', 'woocommerce' ); ?>">
                        <?php
                        if ( $_product->is_sold_individually() ) {
                            $product_quantity = sprintf( '1 <input type="hidden" name="cart[%s][qty]" value="1" />', $cart_item_key );
                        } else {
                            $product_quantity = woocommerce_quantity_input(
                                array(
                                    'input_name'   => "cart[{$cart_item_key}][qty]",
                                    'input_value'  => $cart_item['quantity'],
                                    'max_value'    => $_product->get_max_purchase_quantity(),
                                    'min_value'    => '0',
                                    'product_name' => $_product->get_name(),
                                ),
                                $_product,
                                false
                            );
                        }
                        echo apply_filters( 'woocommerce_cart_item_quantity', $product_quantity, $cart_item_key, $cart_item ); // PHPCS: XSS ok.
                        ?>
                        </td>
                        <td class="product-quantity" data-title="<?php esc_attr_e( 'Discount', 'woocommerce' ); ?>">
                            <!--label for="discount"><?php esc_html_e( 'Discount:', 'woocommerce' ); ?></label> <input type="text" name="discount" class="input-text" id="discount_code" value="" placeholder="<?php esc_attr_e( 'dscto', 'woocommerce' ); ?>" /-->
                                <span>Dscto %:</span>
                                <input class="discount-cart-item" type="text" id="discount_cart_<?php echo $cart_item_key; ?>" class="input-text text" data-cart-id="<?php echo $cart_item_key; ?>" value="<?php echo ( isset( $cart_item[ 'percentage' ] ) ) ? substr($cart_item['percentage'], 0, -1) : ''; ?>" title="<?php esc_attr_e( 'Descuento', 'woocommerce' ); ?>" size="4" placeholder="e.g. 10">
                        </td>

                        <td class="product-subtotal" data-title="<?php esc_attr_e( 'Total', 'woocommerce' ); ?>">
                            <?php
                                echo apply_filters( 'woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal( $_product, $cart_item['quantity'] ), $cart_item, $cart_item_key ); // PHPCS: XSS ok.
                            ?>
                        </td>
                    </tr>
                    <?php
                }
            }
            ?>

            <?php do_action( 'woocommerce_cart_contents' ); ?>

            <tr>
                <td colspan="6" class="actions">

                    <?php if ( wc_coupons_enabled() ) { ?>
                        <div class="coupon">
                            <label for="coupon_code"><?php esc_html_e( 'Coupon:', 'woocommerce' ); ?></label> <input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e( 'Coupon code', 'woocommerce' ); ?>" /> <button type="submit" class="button" name="apply_coupon" value="<?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?>"><?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?></button>
                            <?php do_action( 'woocommerce_cart_coupon' ); ?>
                        </div>
                    <?php } ?>

                    <button type="submit" class="button" name="update_cart" value="<?php esc_attr_e( 'Update cart', 'woocommerce' ); ?>"><?php esc_html_e( 'Update cart', 'woocommerce' ); ?></button>

                    <?php do_action( 'woocommerce_cart_actions' ); ?>

                    <?php wp_nonce_field( 'woocommerce-cart', 'woocommerce-cart-nonce' ); ?>
                </td>
            </tr>

            <?php do_action( 'woocommerce_after_cart_contents' ); ?>
        </tbody>
    </table>
    <?php do_action( 'woocommerce_after_cart_table' ); ?>
</form>

<?php do_action( 'woocommerce_before_cart_collaterals' ); ?>

<div class="cart-collaterals">
    <?php
        /**
         * Cart collaterals hook.
         *
         * @hooked woocommerce_cross_sell_display
         * @hooked woocommerce_cart_totals - 10
         */
        do_action( 'woocommerce_cart_collaterals' );
    ?>
</div>

<?php do_action( 'woocommerce_after_cart' ); ?>

What I am doing wrong? Any idea?

One Answer

You are not saving all the required cart item data as custom order item meta, so when using "Order again", some custom cart item data is missing ad your price calculations doesn't get applied.

Also woocommerce_add_order_item_meta hook is deprecated since WooCommerce 3.

Note: With your provided code, I am not able to change the percentage in cart page, so maybe something is missing.

You will need to remove/replace in your code the following functions:

// Add order item meta.
add_action('woocommerce_add_order_item_meta', 'add_order_item_meta', 10, 3);
function add_order_item_meta($item_id, $cart_item, $cart_item_key)
{
    if (isset($cart_item['percentage'])) {
        wc_add_order_item_meta($item_id, 'percentage', $cart_item['percentage']);
    }
}

and this one too:

add_filter( 'woocommerce_order_again_cart_item_data', 'order_again_custom', 10, 3 );

function order_again_custom($cart_item_meta, $product, $order){
    //Create an array of all the missing custom field keys that needs to be added in cart item.
    $customfields = [
                    'Titulo1', 
        'percentage',
        'custom_price',
        'price',
        'new_price',
                    ];
    global $woocommerce;
    remove_all_filters( 'woocommerce_add_to_cart_validation' );
    if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
    foreach ( $customfields as $key ){
        if(!empty($product[$key])){
            $cart_item_meta[$key] = $product[$key];
        }
    }
    return $cart_item_meta;
}

by the following ones:

// Save custom cart item data as custom order item meta data
add_action( 'woocommerce_checkout_create_order_line_item', 'add_order_item_meta', 10, 4 );
function add_order_item_meta( $item, $cart_item_key, $values, $order ) {
    
    // Save and display the "Percentage" (optional - if needed)
    if (isset($values['percentage'])) {
        $item->update_meta_data( 'Percentage', $values['percentage'] ); 
    }

    // Save All custom cart item data as a hidden data array (important)
    if (isset($values['percentage']) && isset($values['base_price']) && isset($values['new_price']) ) {
        $custom_data = array(
            'percentage' => $values['percentage'],
            'base_price' => $values['base_price'],
            'new_price'  => $values['new_price'],
        );
        $item->update_meta_data( '_custom_data', $custom_data ); // save
    }
}

// Add custom order item meta as custom cart item meta
add_filter( 'woocommerce_order_again_cart_item_data', 'custom_cart_item_data_for_order_again', 10, 3 );
function custom_cart_item_data_for_order_again( $cart_item_meta, $item, $order ) {
    // Get the hidden order item data
    $custom_data = (array) $item->get_meta( '_custom_data' );

    if( ! empty($custom_data) ) {
        $cart_item_meta = array_merge( $cart_item_meta, $custom_data );
    }
    return $cart_item_meta;
}

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

Answered by LoicTheAztec on December 22, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP