I ran into an interesting problem recently for a consulting project where the client needed to know what orders have had refunds processed in WooCommerce.

While helping out with this problem, I found that there isn’t a great way to get all refunded orders at once, only to get refunds for a particular order.

There are a couple ways you could approach this issue. Let’s start with the couple methods they’d tried first:

Get all orders with the status wc-refunded

While you could do this, this option was nixed pretty quickly. As orders can be partially refunded, they likely won’t have this order status. As such, this would only give us fully refunded orders, and not all orders that have a refund associated with them.

Loop over orders and check for refunds

  1. Get all order posts (shop_order post type) using get_posts()
  2. Loop over them to get the WC_Order order object for each one
  3. Check if the order has refunds using $order->get_refunds()
  4. If not empty (there are refunds), push the order (or just its ID) into a new array
  5. This array makes up all orders that have been refunded with at least one refund recorded.

The function to do so probably looks something like this:

function sv_get_refunded_orders() {

    $query_args = array(
        'post_type'      => wc_get_order_types(),
        'post_status'    => array_keys( wc_get_order_statuses() ),
        'posts_per_page' => 999999999999,
    );

    $all_orders      = get_posts( $query_args );
    $refunded_orders = array();

    foreach ( $all_orders as $order ) {

        $order = wc_get_order( $order->ID );

        // sanity check
        if ( ! is_object( $order ) ) {
            continue;
        }

        // do this outside of empty() - PHP 5.2-5.4
        $refunds = $order->get_refunds();
        if ( ! empty( $refunds ) ) {
            $refunded_orders[] = $order;
        }
    }

    return $refunded_orders;
}

However, this is going to be an expensive process just to figure out which orders have refunds for a few reasons:

  • If the shop you’re working on has tens of thousands of orders, you’re getting a massive array of order objects to loop through
  • Since you have the WP_Post object from get_posts(), you then have to work to get the order object before you can check for refunds
  • Since refunded orders are probably a small percentage, that’s a lot of checking just to get a small percentage of overall orders

Granted, we could get only the IDs returned from our get_posts() query instead of all the WP Post objects (by setting 'fields' => 'ids'), but the point about doing a lot of looping for nothing still remains.

With a bit more knowledge of how refund data is stored in WooCommerce, we can drastically simplify this process and do a far more direct query for all orders that have a refund associated with them.

Get WooCommerce Refunded Orders

The easiest way to get WooCommerce refunded orders is…query directly for the refunded orders. Refunds are stored as their own custom post type, sort of like an inverse or shadow of the original order with all of the totals or line items that should be substracted as a result of the refund.

WooCommerce refunds use the post type shop_order_refund, and each refund post is a child post of the original order’s post. This means that, if we query all of these posts instead, we go directly to the source — refund data — and can work backwards to get the orders associated with the refunds.

The id=>parent fields parameter is particularly helpful for this. When used in a get_posts() query, this will not return full post objects, but rather, an associative array like this:

array(
   [ $post_id ] => [ $parent_post_id ],
);

In our case, this is exactly what we need. We don’t really care about the rest of the refunded post’s data; we just want its parent ID, which is what the fields return parameter is giving us here — all of the parent posts (orders) with a refund.

Let’s start with our get_posts() query:

$query_args = array(
    'fields'         => 'id=>parent',
    'post_type'      => 'shop_order_refund',
    'post_status'    => 'any',
    'posts_per_page' => 999999999999,
);

$refunds = get_posts( $query_args );

Alright, let’s see what that gives us:

Array
(
    [3300] => 3279
    [3301] => 3220
    [3302] => 3221
    [3303] => 3220
    [2767] => 2766
)

Perfect! We’ve got an array of $refund_post_id => $order_id now for every single refund recorded.

Now notice that the parent order ID (array value) 3220 is repeated — this is because an order can have more than one refund associated with it. Since we don’t need to know the order ID twice, let’s amend this to get only unique values:

$refunded_orders = array_unique( get_posts( $query_args ) );

Alright! Now we’ve got something more like this:

Array (
    [3300] => 3279
    [3301] => 3220
    [3302] => 3221
    [2767] => 2766
)

So for the final touch — let’s get a simple array of only order IDs that have refunds. We only need the array values, so something like this will do it:

$refunded_orders = array_values( $refunded_orders );

Now we have exactly what we want: an array of order IDs for orders that have at least one refund associated with them:

Array (
    [0] => 3279
    [1] => 3220
    [2] => 3221
    [3] => 2766
)

Note that this step isn’t really required — if you had use for the refund post’s ID, or if you were just going to immediately loop through these like foreach ( $orders as $order_id ), you could leave the array as-is without isolating the values and thus omit the array_values() step.

However, since we’re building this as a general helper function, let’s ensure we return exactly what we say we will — an array of order IDs with refunds attached to them.

Now we’ll put this all together in a helper function we can use for our site or project:

/**
 * Returns an array of order IDs that have a refund associated with the order
 *
 * @return array $refunded_order_ids
 */
function sv_get_wc_orders_with_refunds() {

    $query_args = array(
        'fields'         => 'id=>parent',
        'post_type'      => 'shop_order_refund',
        'post_status'    => 'any',
        'posts_per_page' => -1,
    );

    $refunds = get_posts( $query_args );
    
    return array_values( array_unique( $refunds ) );
}

Now sv_get_wc_orders_with_refunds() can be used as needed to give you a list of WooCommerce refunded orders, including both partial and full refunds 🙂

Published by Beka Rice

Beka manages WooCommerce product direction for SkyVerge, such as plugin roadmaps, documentation, and sales copy. She typically gets stuck with boring administrative tasks, but likes to write so she has an excuse to spend more time jamming out to anything from The Clash to Lady Gaga.

1 Comment

  1. […] developers: SkyVerge has a quick guide on creating a helper function to return all WooCommerce orders with a refund processed (either fully or partially […]

    Reply

Leave a Reply

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

Error: Please enter a valid email address

Error: Invalid email

Error: Please enter your first name

Error: Please enter your last name

Error: Please enter a username

Error: Please enter a password

Error: Please confirm your password

Error: Password and password confirmation do not match