Χρέωση αντικαταβολής στο WooCommerce χωρίς plugin

Cash on Delivery is still one of the most common payment methods in WooCommerce stores. It is also one of the most expensive ones to operate. For that reason, many stores apply a flat extra fee when COD is selected.

WooCommerce does not provide a native setting to add fees per payment method. Plugins exist, but for a simple, predictable setup, adding one more dependency to the checkout flow is often unnecessary.

A small, well-placed snippet is usually enough.
The important part, especially today, is understanding where and when that snippet runs.

Two checkouts, two different behaviors

Modern WooCommerce sites can use one of two checkout systems, and they behave very differently.

  • Classic checkout (shortcode-based)
    This is the traditional checkout rendered via PHP templates. It relies on jQuery, AJAX requests, and the update_checkout event to recalculate totals when something changes. It is still widely used, especially in custom themes and long-lived stores.
  • Block checkout (WooCommerce Blocks)
    This is the newer checkout experience built on top of the Store API and React state. There is no jQuery-driven refresh cycle, and session updates are not always immediately available during state transitions.

The baseline solution: simple and stable

Let’s start with the clean, minimal requirement: Add a flat extra fee when Cash on Delivery is selected. No shipping checks, no local pickup logic, no edge cases.

PHP
/**
 * WooCommerce - Add a flat Cash on Delivery (COD) fee
 * Works with both classic shortcode checkout and Block checkout.
 */
add_action( 'woocommerce_cart_calculate_fees', function( $cart ) {

    if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
        return;
    }

    if ( ! WC()->session || ! $cart ) {
        return;
    }

    $cod_fee = 3; // Set your fee amount here.

    // Primary source: WooCommerce session
    $chosen_payment_method = WC()->session->get( 'chosen_payment_method' );

    // Fallback for cases where session is not yet synced (common during checkout updates)
    if ( $chosen_payment_method !== 'cod' ) {
        if ( isset( $_POST['payment_method'] ) ) {
            $chosen_payment_method = sanitize_text_field( wp_unslash( $_POST['payment_method'] ) );
        } elseif ( isset( $_REQUEST['payment_method'] ) ) {
            $chosen_payment_method = sanitize_text_field( wp_unslash( $_REQUEST['payment_method'] ) );
        }
    }

    if ( $chosen_payment_method !== 'cod' ) {
        return;
    }

    // Prevent duplicate fees on recalculation
    foreach ( $cart->get_fees() as $fee ) {
        if ( isset( $fee->id ) && $fee->id === 'cod_fee' ) {
            return;
        }
    }

    $cart->add_fee(
        __( 'Αντικαταβολή', 'woocommerce' ),
        $cod_fee,
        false,
        'cod_fee'
    );

}, 20 );

This snippet is intentionally conservative:

  • It uses woocommerce_cart_calculate_fees, which is still the correct hook.
  • It prefers the WooCommerce session but includes safe fallbacks.
  • It avoids duplicate fees when the cart recalculates.
  • It does not rely on plugins, filters, or checkout overrides.

For many stores, this is all that is needed.

Classic checkout: why the fee sometimes does not update

In the classic checkout flow, WooCommerce is supposed to refresh totals automatically when the payment method changes. The core checkout script listens for radio button changes and triggers update_checkout.

In practice, this does not always happen.

  • custom checkout templates
  • themes that dequeue or delay wc-checkout.js
  • JavaScript errors elsewhere on the page
  • aggressive performance optimizations

The result is simple: the payment method changes, but the fee does not.

PHP
/**
 * Force checkout refresh when payment method changes (classic checkout).
 */
add_action( 'wp_footer', function () {
    if ( ! is_checkout() || is_wc_endpoint_url( 'order-received' ) ) {
        return;
    }
    ?>
    <script>
      (function($){
        if (!window.jQuery) return;

        $(document.body).on('change', 'input[name="payment_method"]', function () {
          $(document.body).trigger('update_checkout');
        });
      })(jQuery);
    </script>
    <?php
}, 20 );

Block checkout: no events, different assumptions

The Block checkout does not use update_checkout. There is no jQuery event cycle to hook into. Recalculations happen through Store API state updates, and the selected payment method may briefly exist outside the PHP session during transitions.