Woocommerce, Woocommerce CRM

WooCommerce Refund Request Button on My Account Page

WooCommerce refund request button

Handling refund requests through emails or support tickets can quickly become messy, especially when you’re managing multiple WooCommerce orders every day. A much cleaner approach is to let customers request refunds directly from their My Account area.

In this tutorial, you’ll learn how to add a custom “Request Refund” button inside the WooCommerce Orders section without installing any extra plugins. The solution uses lightweight PHP, JavaScript, and native HTML only, making it fast, flexible, and easy to maintain.

Once implemented, customers will be able to submit refund requests from their order list, provide a refund reason inside a popup form, and automatically notify the store admin. The request will also be stored as an order note so everything remains connected to the original purchase.

Even better, once a request has been submitted, the refund button automatically changes to a disabled “Refund Requested” status to prevent duplicate submissions.

What This WooCommerce Snippet Does

  • Adds a “Request Refund” button inside My Account orders
  • Displays the button only for eligible orders
  • Restricts refund requests to recent purchases
  • Opens a native popup modal for refund details
  • Saves refund requests as WooCommerce order metadata
  • Adds a customer note to the order timeline
  • Sends an automatic admin notification email
  • Prevents duplicate refund submissions

WooCommerce PHP Snippet: Add Refund Request Button to My Account Orders

The following snippet creates a complete refund request workflow directly inside WooCommerce customer accounts.

1. Add Refund Request Action to Orders

This section inserts a custom refund button into WooCommerce order actions. The button only appears for processing or completed orders placed within the last 60 days.

If a customer already submitted a refund request, the button changes into a disabled “Refund Requested” label.


/**
 * Add Refund Request Button in WooCommerce My Account Orders
 */

add_filter( 'woocommerce_my_account_my_orders_actions', 'custom_wc_refund_request_action', 10, 2 );

function custom_wc_refund_request_action( $actions, $order ) {

    if ( is_order_received_page() ) {
        return $actions;
    }

    $allowed_statuses = array( 'processing', 'completed' );

    if ( in_array( $order->get_status(), $allowed_statuses, true ) ) {

        $already_requested = $order->get_meta( '_custom_refund_request_sent' );

        if ( $already_requested ) {

            $actions['refund-requested'] = array(
                'url'  => '#',
                'name' => 'Refund Requested',
            );

            return $actions;
        }

        $created_date = $order->get_date_created();

        if ( $created_date ) {

            $days_old = ( time() - $created_date->getTimestamp() ) / DAY_IN_SECONDS;

            if ( $days_old <= 60 ) {

                $actions['request-refund'] = array(
                    'url'        => '#refund-order-' . $order->get_id(),
                    'name'       => 'Request Refund',
                    'aria-label' => 'Request Refund',
                );
            }
        }
    }

    return $actions;
}

2. Style the Disabled Refund Button

This optional CSS visually disables the button after a refund request has already been submitted.


add_action( 'wp_head', 'custom_wc_refund_button_styles' );

function custom_wc_refund_button_styles() {
    ?>
    <style>
        a.refund-requested {
            opacity: 0.5;
            pointer-events: none;
            cursor: not-allowed !important;
        }
    </style>
    <?php
}

3. Create Refund Request Popup Modal

This section outputs a native HTML dialog popup and handles customer interaction using JavaScript.

add_action( 'woocommerce_after_account_orders', 'custom_wc_refund_popup' );
add_action( 'woocommerce_view_order', 'custom_wc_refund_popup' );

function custom_wc_refund_popup() {
    ?>

    <dialog id="custom-refund-popup">

        <form method="dialog" id="custom-refund-form">

            <h3>Submit Refund Request</h3>

            <input type="hidden" id="custom-order-id" value="">

            <label for="custom-refund-reason">
                Reason for Refund
            </label>

            <textarea id="custom-refund-reason" required style="width:100%;height:120px;"></textarea>

            <div style="margin-top:15px;">
                <button id="custom-refund-close">Cancel</button>
                <button id="custom-refund-send">Send Request</button>
            </div>

        </form>

    </dialog>

    <script>

    document.querySelectorAll('a.request-refund').forEach(function(button){

        button.addEventListener('click', function(event){

            event.preventDefault();

            let orderID = this.getAttribute('href').replace('#refund-order-', '');

            document.getElementById('custom-order-id').value = orderID;

            document.getElementById('custom-refund-reason').value = '';

            document.getElementById('custom-refund-popup').showModal();
        });

    });

    document.getElementById('custom-refund-close').addEventListener('click', function(e){

        e.preventDefault();

        document.getElementById('custom-refund-popup').close();

    });

    document.getElementById('custom-refund-send').addEventListener('click', function(e){

        e.preventDefault();

        let orderID = document.getElementById('custom-order-id').value;

        let reason = document.getElementById('custom-refund-reason').value;

        if ( !orderID || reason.trim() === '' ) {
            return;
        }

        fetch('<?php echo admin_url( 'admin-ajax.php' ); ?>', {

            method: 'POST',

            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },

            body: new URLSearchParams({

                action: 'custom_submit_refund_request',

                order_id: orderID,

                refund_reason: reason,

                security: '<?php echo wp_create_nonce( 'custom_refund_nonce' ); ?>'

            })

        })

        .then(response => response.json())

        .then(data => {

            document.getElementById('custom-refund-popup').close();

            if ( data.success ) {

                alert('Refund request submitted successfully.');

                location.reload();

            } else {

                alert(data.message || 'Something went wrong.');

            }

        });

    });

    </script>

    <?php
}

4. Handle Refund Request Submission with AJAX

This final section processes the request, saves order metadata, creates a customer order note, and emails the store admin.


add_action( 'wp_ajax_custom_submit_refund_request', 'custom_submit_refund_request' );
add_action( 'wp_ajax_nopriv_custom_submit_refund_request', 'custom_submit_refund_request' );

function custom_submit_refund_request() {

    if (
        empty( $_POST['security'] ) ||
        ! wp_verify_nonce( $_POST['security'], 'custom_refund_nonce' )
    ) {
        wp_send_json_error( array(
            'message' => 'Security validation failed.'
        ) );
    }

    if (
        empty( $_POST['order_id'] ) ||
        empty( $_POST['refund_reason'] )
    ) {
        wp_send_json_error( array(
            'message' => 'Required fields are missing.'
        ) );
    }

    $order_id = absint( $_POST['order_id'] );

    $refund_reason = sanitize_textarea_field( $_POST['refund_reason'] );

    $order = wc_get_order( $order_id );

    if ( ! $order ) {

        wp_send_json_error( array(
            'message' => 'Invalid order.'
        ) );
    }

    $order->update_meta_data(
        '_custom_refund_request_date',
        current_time( 'mysql' )
    );

    $order->update_meta_data(
        '_custom_refund_request_reason',
        $refund_reason
    );

    $order->update_meta_data(
        '_custom_refund_request_sent',
        'yes'
    );

    $order->save();

    $order->add_order_note(
        'Customer submitted a refund request: ' . $refund_reason,
        true
    );

    wp_mail(

        get_option( 'admin_email' ),

        'New Refund Request for Order #' . $order_id,

        "A customer submitted a refund request.\n\n" .
        "Order ID: #" . $order_id . "\n\n" .
        "Refund Reason:\n" . $refund_reason . "\n\n" .
        "Manage Order:\n" .
        admin_url( 'post.php?post=' . $order_id . '&action=edit' )

    );

    wp_send_json_success( array(
        'message' => 'Refund request submitted.'
    ) );
}

Where Should You Add This Code?

Add the PHP snippets to your child theme’s functions.php file or use a custom snippets plugin. CSS can be added inside your child theme stylesheet if preferred.

Before testing, temporarily switch to a default WooCommerce theme like Storefront and disable unrelated plugins to rule out possible conflicts.

Explore More Blogs : WooCommerce Automatically Log Out Customers After Checkout