Search code examples
phpwordpresswoocommerceorderscredits

Alter WooCommerce order total efficiently instead of using set_total method


My customers can pay for their order via store credit (saved as user data), partly or completely. In case the store credit is enough to cover for the whole order, the customer can only choose Store credit payment method and the order is passed as paid (and the customer's credit adjusted).

But for part payment, I subtract the store credit from the order's total so that the customer is only left with part of the order to pay via the payment method of their choice (here the store credit method becomes unavailable).

To achieve this, I alter the order's total using $order->set_total:

function update_customer_credit_and_display_payment_method($order) {
    //Calculate new total
    $order_total = (float) WC()->cart->total;
    $customer_id = get_current_user_id();
    if (!$customer_id ) { return; }

    $customer_credit = get_deposit_credit($customer_id);
    $applied_credit = 0;

    if ($order_total > $customer_credit && $customer_credit > 0) {
        $actual_payment_method = $order->get_payment_method_title();
        $applied_credit = $customer_credit;
        $payment_method = $actual_payment_method . ' & Deposit Credit (' . wc_price($applied_credit) . ')';

        $order->set_payment_method_title($payment_method);
        $order->set_total($order_total - $applied_credit);
        $order->save();
    }

}
add_action('woocommerce_checkout_create_order', 'update_customer_credit_and_display_payment_method', 10, 1);

The problem is that when I pass an order from On hold to Processing (in case someone pays by cheque or bank transfer), it reverts the order's total to its original value (before the deposit is applied). Also happens when I set the order to completed. Is this a bug or am I not using the right function? Is there a way to alter the order's total for good? (without altering the price of products and taxes). Thank you.


Solution

  • You can use the following alternative, to avoid this issue, replacing your code with:

    // Add customer credit and update payment method title
    add_action('woocommerce_checkout_create_order', 'add_customer_credit_and_update_payment_method_title', 10 );
    function add_customer_credit_and_update_payment_method_title( $order ) {
        if ( ! is_user_logged_in() ) return;
        // Get customer deposit credit
        $deposit_credit = (float) get_deposit_credit( get_current_user_id() );
    
        if ( $deposit_credit > 0 && WC()->cart->get_total('edit') > $deposit_credit ) {
            // Add deposit_credit as order custom meta data
            $order->add_meta_data( 'deposit_credit', $deposit_credit, true ); 
            // Update payment title
            $order->set_payment_method_title( sprintf(
                __('%s & Deposit Credit (%s)', 'woocommerce'), 
                $order->get_payment_method_title(), 
                wc_price( $deposit_credit, array( 'currency' => $order->get_currency() ) )
            ));
            // The save() method is not needed as it's already included just after this hook;
        }
    }
    
    // Remove dynamically the deposit credit from order total
    add_filter( 'woocommerce_order_get_total', 'remove_deposit_credit_from_order_total', 10, 2 );
    function remove_deposit_credit_from_order_total( $total, $order ) {
        return $total - floatval($order->get_meta('deposit_credit'));
    }
    
    // Display in admin single order line totals, the deposit credit
    add_filter( 'woocommerce_admin_order_totals_after_tax', 'display_admin_deposit_credit_line_total', 10 );
    function display_admin_deposit_credit_line_total( $order_id ) {
        $order = wc_get_order($order_id);
        if ( $deposit_credit = floatval($order->get_meta('deposit_credit')) ) {
            echo '<tr>
            <td class="label">' .esc_html( 'Deposit Credit', 'woocommerce' ) . ':</td>
            <td width="1%"></td>
            <td class="total">' . wc_price( -$deposit_credit, array( 'currency' => $order->get_currency() ) ) . '</td>
            </tr>';
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). Tested and works.