Reserve Your Free Seat for Our Next WooCommerce Class! Search
Business Bloomer
  • Join
  • WooCommerce Plugins
  • WooCommerce Tips
  • Log In
  • 0
  • Business Bloomer Club
  • WooCommerce Plugins
  • WooCommerce Tips
  • Log In
  • Search
  • Contact
  • Cart
WooCommerce Code Snippets My Account Order Refund

WooCommerce: Refund Request Button @ My Account

Last Revised: Nov 2025

STAY UPDATED

In WooCommerce, giving customers a simple way to request a refund can save you time and improve their experience.

While many store owners handle refunds manually via emails or support tickets, adding a dedicated “Refund Request” button directly in the My Account page makes the process seamless for both customers and store admins. This approach keeps all refund requests tied to the original order, ensures proper logging via customer notes, and automatically notifies the admin when a request is submitted.

In this tutorial, we’ll create a lightweight solution using only PHP, JS and core HTML—no additional plugins or frameworks required.

You’ll learn how to display a new action on the My Account orders table, open a native HTML modal for customers to enter their reason, and handle the request by adding a customer note and sending an admin email. By the end, your WooCommerce store will have a professional, fully functional refund request workflow ready to go.

Here’s the brand new “Orders” view, with the “Ask for a Refund” buttons and a greyed out “Pending Refund” in case it’s already been requested.

PHP Snippet: Enable WooCommerce Refund Requests in My Account

This WooCommerce snippet adds a customer-facing refund request system directly within the My Account area.

Action Button Filter: The first section adds a “Ask for a Refund” button to order action lists (orders page, single order view). It only appears for completed or processing orders within 60 days. If a refund has already been requested, the button changes to “Pending Refund” and becomes non-clickable.

CSS Styling: A small inline style greys out the pending refund button to visually indicate it’s disabled.

Modal Dialog & JavaScript: This outputs an HTML dialog element with a textarea for the refund reason. JavaScript handlers open the modal when clicking the refund button (extracting the order ID from the URL hash), handle cancel/submit actions, and make an AJAX call to WordPress. After successful submission, the page reloads to show the updated button state.

AJAX Handler: Processes the refund request by verifying the nonce and user authorization, then stores the request timestamp and reason as order meta. It adds a customer note to the order and sends an email notification to the site admin with order details and a direct link.

/**
 * @snippet       Refund Button @ WooCommerce My Account Order Actions
 * @tutorial      https://businessbloomer.com/woocommerce-customization
 * @author        Rodolfo Melogli, Business Bloomer
 * @compatible    WooCommerce 10
 * @community     Join https://businessbloomer.com/club/
 */

/**
 * Add "Request Refund" action on My Account > Orders
 */
add_filter( 'woocommerce_my_account_my_orders_actions', function( $actions, $order ) {
	
	// Don't show on thank you page
    if ( is_order_received_page() ) {
        return $actions;
    }

    // Show only for completed or processing orders (customize as needed)
    if ( in_array( $order->get_status(), ['completed', 'processing'] ) ) {
		
		// Check if refund already requested
    	$refund_requested = $order->get_meta( '_bb_refund_requested' );		
		if ( $refund_requested ) {
			// Show greyed out button
			$actions['bb-refund-pending'] = [
				'url'  => '#',
				'name' => '✓ Pending Refund',
			];
			return $actions;
		}
		
		// Check if order is within 60 days
        $order_date = $order->get_date_created();
        if ( $order_date ) {
            $days_since_order = ( time() - $order_date->getTimestamp() ) / DAY_IN_SECONDS;

            if ( $days_since_order <= 60 ) {

				$actions['bb_request_refund'] = [
					'url'  => '#refund-' . $order->get_id(),
					'name' => 'Ask for a Refund',
					'aria-label' => 'Ask for a Refund',
				];
				
			}
			
		}
		
    }

    return $actions;
}, 10, 2 );

/**
 * Add CSS to grey out pending refund button
 */
add_action( 'wp_head', function() {
    ?>
    <style>
        a.bb-refund-pending {
            opacity: 0.5;
            cursor: not-allowed !important;
            pointer-events: none;
        }
    </style>
    <?php
});

/**
 * Output the HTML dialog modal + small JS handler
 */
add_action( 'woocommerce_view_order', 'bb_output_refund_modal' );
add_action( 'woocommerce_after_account_orders', 'bb_output_refund_modal' );

function bb_output_refund_modal() {
    ?>

    <dialog id="bb-refund-dialog">
        <form method="dialog" id="bb-refund-form">
            <h3>Request a Refund</h3>

            <input type="hidden" name="order_id" id="bb-order-id" value="">
		            
            <label for="bb-reason">Reason for refund:</label>
            <textarea id="bb-reason" name="reason" required style="width:100%;height:120px;"></textarea>

            <div style="margin-top:1rem;">
                <button id="bb-refund-cancel">Cancel</button>
                <button id="bb-refund-submit">Submit</button>
            </div>
        </form>
    </dialog>

    <script>
        // Open dialog when clicking the custom action
        document.querySelectorAll('a.bb_request_refund').forEach(function(btn){
            btn.addEventListener('click', function(e){
                e.preventDefault();
				const orderID = this.getAttribute('href').replace('#refund-', '');
                document.getElementById('bb-order-id').value = orderID;
				document.getElementById('bb-reason').value = '';
                document.getElementById('bb-refund-dialog').showModal();
            });
        });
	
		  // Cancel button
        document.getElementById('bb-refund-cancel').addEventListener('click', function(e){
            e.preventDefault();
            document.getElementById('bb-refund-dialog').close();
        });

        // Submit button
        document.getElementById('bb-refund-submit').addEventListener('click', function(e){
            e.preventDefault();

            const orderID = document.getElementById('bb-order-id').value;
            const reason  = document.getElementById('bb-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: 'bb_submit_refund_request',
                    order_id: orderID,
                    reason: reason,
					nonce: '<?php echo wp_create_nonce( "bb_refund_nonce" ); ?>'
                })
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('bb-refund-dialog').close();
                if(data.success) {
                    alert('Your refund request has been submitted.');
					location.reload();
                } else {
                    alert('Error: ' + (data.message || 'Something went wrong'));
                }
            })
            .catch(() => {
                alert('Network error. Please try again.');
            });
        });
    </script>

    <?php
}

/**
 * AJAX handler: add customer note + send admin email
 */
add_action( 'wp_ajax_bb_submit_refund_request', 'bb_submit_refund_request' );
add_action( 'wp_ajax_nopriv_bb_submit_refund_request', 'bb_submit_refund_request' );

function bb_submit_refund_request() {

    // Verify nonce
    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'bb_refund_nonce' ) ) {
        wp_send_json_error( ['message' => 'Security check failed'] );
    }

    if ( empty( $_POST['order_id'] ) || empty( $_POST['reason'] ) ) {
        wp_send_json_error( ['message' => 'Missing required fields'] );
    }

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

    $order = wc_get_order( $order_id );
    if ( ! $order ) {
        wp_send_json_error( ['message' => 'Invalid order'] );
    }
	
	 // Mark as refund requested
    $order->update_meta_data( '_bb_refund_requested', current_time( 'mysql' ) );
    $order->update_meta_data( '_bb_refund_reason', $reason );
    $order->save();

    // 1) Add customer note
    $order->add_order_note( 'Customer requested a refund: ' . $reason, true );

    // 2) Notify admin
    wp_mail(
        get_option( 'admin_email' ),
        'Refund Request for Order #' . $order_id,
        "A customer has requested a refund.\n\nOrder: #{$order_id}\nReason:\n{$reason}\n\nView order: " . admin_url( 'post.php?post=' . $order_id . '&action=edit' )
    );
	
	 wp_send_json_success( ['message' => 'Refund request submitted'] );

}

Where to add custom code?

You should place custom PHP in functions.php and custom CSS in style.css of your child theme: where to place WooCommerce customization?

This code still works, unless you report otherwise. To exclude conflicts, temporarily switch to the Storefront theme, disable all plugins except WooCommerce, and test the snippet again: WooCommerce troubleshooting 101

Related content

  • WooCommerce: Separate Login, Registration, My Account Pages
    There are times when you need to send logged out customers to a Login page and unregistered customers to a standalone Register page. As you…
  • WooCommerce: Add New Tab @ My Account Page
    One of the features of Business Bloomer Club is the provision of Premium WooCommerce Q&A Support to supporters who enroll. So, how to add an…
  • WooCommerce: How To Make A Website GDPR Compliant? (12 Steps)
    Ok, we all know that the EU General Data Protection Regulation (GDPR) will come into force on the 25th May 2018. So the main question…
  • WooCommerce: Get Order Data (total, items, etc) From $order Object
    As a WooCommerce development freelancer, every day I repeat many coding operations that make me waste time. One of them is: “How to get ____…
  • WooCommerce: How to Add a Custom Checkout Field
    Let’s imagine you want to add a custom checkout field (and not an additional billing or shipping field) on the WooCommerce Checkout page. For example,…

Rodolfo Melogli

Business Bloomer Founder

Author, WooCommerce expert and WordCamp speaker, Rodolfo has worked as an independent WooCommerce freelancer since 2011. His goal is to help entrepreneurs and developers overcome their WooCommerce nightmares. Rodolfo loves travelling, chasing tennis & soccer balls and, of course, wood fired oven pizza. Follow @rmelogli

2 thoughts on “WooCommerce: Refund Request Button @ My Account”

  1. Damien Carbery
    November 26, 2025

    Great idea – better than mine where I used a CF7 form on a specified page but didn’t prevent duplicate requests (https://www.damiencarbery.com/2016/11/refund-requests-for-woocommerce-customers-using-contact-form-7/)

    Aside: I suggest removing the nopriv ajax handler as the user will have to be logged in to see the button and no non-logged in users should be able to submit a refund request. I know you have nonce protection; this would just be an extra layer of security.

    Reply
    1. Rodolfo Melogli
      December 2, 2025

      Thank you and good catch, will do!

      Reply
Questions? Feedback? Customization? Leave your comment now!
_____

If you are writing code, please wrap it like so: [php]code_here[/php]. Failure to complying with this, as well as going off topic or not using the English language will result in comment disapproval. You should expect a reply in about 2 weeks - this is a popular blog but I need to get paid work done first. Please consider joining the Business Bloomer Club to get quick WooCommerce support. Thank you!

Cancel reply

Your email address will not be published. Required fields are marked *


Search WooCommerce Tips

Popular Searches: Visual Hook Guides - Checkout Page - Cart Page - Single Product Page - Add to Cart - Emails - Shipping - Prices - Hosting

Recent Articles

  • WooCommerce: Simple Price Including/Excluding Tax Switcher
  • WooCommerce: Refund Request Button @ My Account
  • WooCommerce: Show or Hide Bank Accounts Based On Order
  • WooCommerce: Save Order Currency Exchange Rate
  • WooCommerce: Get Orders Containing a Specific Product

Latest Comments

  1. Rodolfo Melogli on WooCommerce: Add Checkout Fee for a Payment Gateway (e.g. PayPal)
  2. Rodolfo Melogli on WooCommerce Orders With No Customer and Zero Value
  3. Johnny on WooCommerce Orders With No Customer and Zero Value

Find Out More

  • Become a WooCommerce Expert
  • Business Bloomer Club
  • WooCommerce Blog
  • WooCommerce Weekly
  • Contact

Contact Info

Ciao! I'm Rodolfo Melogli, an Italian Civil Engineer who has turned into an international WooCommerce expert. You can contact me here:

Twitter: @rmelogli

Get in touch: Contact Page

Business Bloomer © 2011-2025 - VAT IT02722220817 - Terms of Use - Privacy Policy

Cart reminder?

x