How to Bulk Refund Orders in WooCommerce Programmatically

While running your WooCommerce store it might become necessary to refund a large number of customer orders.
Refunding an order is possible via the WordPress dashboard, but if the number of orders is more than 100 you might want to do this programmatically using a script and WP CLI.

Here are some examples of why you might need to refund large numbers of orders:

  • An online class is cancelled.
  • Vendor did not deliver stock.
  • Product stock was incorrect and items were oversold.
  • Product price has changed and partial refund will be given to customers.

For this how to you’re going to need shell or ssh access to the server you’ll be working on. In this post I’m going to share parts of a script we use to bulk refund large numbers of WooCommerce orders programmatically.

  1. Get input for the script
  2. WooCommerce Helper Functions
  3. Organizing the WP CLI script
  4. Programmatically refunding a WooCommerce order
  5. Check the script output

Now, let’s get started with the script!

Getting input for the script

The first data to collect for this process is the order_id’s for each order that will be refunded. You can use your reporting tool of choice to gather these ids. Most of our clients use Metorik for reporting and with that tool they can identify the orders that need refunds.

You will also need to have a reason for the refund. This is a short string that will be added to the WooCommerce order notes explaining why a refund was issued. Each order can have a different amount to refund, or you can set the amount to be the same for each order.

The last two questions to answer for each order are 1) if the payment should be refunded and 2) if the items should be restocked. If the payment should be refunded be sure that the payment gateway used to pay for the order supports refunds. The WooCommerce helper function will attempt to contact the gateway to refund money during the script execution. If items need to be restocked or re-added to the product’s quantity item then this can be added to the last part of each order’s row.

Here’s a list of the fields we’ve discussed:

1. order_id - integer. Example: 5002
2. reason - string. Example: "Item oversold."
3. amount - float. "35.84"
4. refund_payment - string Example: "yes"
5. restock_items - string

Add this data to a CSV file and we’ll use that as the input for the refund script.

WooCommerce Helper Functions

The script is going to use two helper functions to handle the bulk of the work.

The first helper function is wc_get_order(). This function will take the order_id and utilize the WC_Order_Factory class to get the order object. We will use this order object to make sure the order exists, and get the order’s total.

The second helper function is wc_create_refund(). This function will take an array of arguments and create the refund for the order. The result from this function will be kept for checking on the success or failure of the refund process.

Organizing the WP CLI script

As always we’ll be using WP CLI to run this script from the command line. I’m going to name the script refund.php and place it in a protected area of the website not accessible to the outside world.

The script will take one parameter to set the name of the CSV file.

if ( false === ( $handle = fopen( $args[0],'r' ) ) ) { die( 'File not found' ); }

The script is going to loop through each row of the CSV file and setup the order specific variables and $args array:

while ( ( $cols = fgetcsv( $handle ) ) !== FALSE ) {
     // Get values from row
     $order_id = $cols[0];
     $reason   = $cols[1];
     $amount   = $cols[2];
     $refund   = $cols[3];
     $restock  = $cols[4];

     // This is a little verbose, but doing it this way to show source of values. 
     $args = array(
           'amount'         => $amount,
           'reason'         => $reason,
           'order_id'       => $order_id,
           'refund_payment' => $refund,
           'restock_items'  => $restock,
     );

Next, the WooCommerce helper function is used to get the order and do some basic verification on it’s status and order total.

$order = wc_get_order( $order_id );

if ( ! $order ) {
	WP_CLI::log( "Order $order_id does not exist!" );
	continue;
}

if ( ! $order->get_total() ) {
	WP_CLI::log( "Order $order_id total is zero." );
	continue;
}

if ( $order->has_status( 'refunded' ) ) {
	WP_CLI::log( "Order $order_id already refunded." );
	continue;
}

// Is amount below total?
if ( $amount > $order->get_total() ) {
	WP_CLI::log( $order_id . ' refund amount more than order total - csv: ' . $amount . ', order: ' . $order->get_total() );
	continue;
}

Finally the order refund is created and is counted as successful if the result is not a WP_Error.

// Process the refund
$result = 0;
try {
	$result = wc_create_refund( $args );
} catch ( Exception $e ) {
	WP_CLI::log( 'Error refunding order: ' . $e->getMessage() );
}

if ( is_wp_error( $result ) ) {
	WP_CLI::log( 'An error occurred: ' . $result->get_error_message() . ' ' . $order_id );
} else {
	WP_CLI::success( 'Refund successful ' . $order_id );
}

Here is a GitHub gist with the full example script used in this how to.

Programmatically refunding a WooCommerce order

To be sure the script is working as expected we highly suggest that you run it in a staging environment with the refund flag set to “no” or run the script with a CSV file containing two or three orders.

Once the script is validated you can do the deal on the live server with a command like this:

$ wp cli eval-file refund.php refund_orders.csv

The script will begin and output details for each order refund.

Check the script output

Example output might look like this:

Start...
Refund successful 40034
Refund successful 40036
Refund successful 40045
Refund successful 40057
Order 40062 already refunded.
Refund successful 40066
Order 40069 total is zero.
Refund successful 40073
Refund successful 40076
Done

Out of the group of example orders refunded two have a notice that a shop manager would want to investigate further. There may be a small number of orders that have an error refunding, or have already been refunded. You can check these individually as the number of anomalies will be much smaller than the total group of orders.

Final thoughts

Refunding WooCommerce orders programmatically is a process that can be setup and kept in a site manager’s toolbox to be used if the need arises.

Some things that were not covered in this how to are how to handle taxes and how to handle payment gateways that do not support full or partial order refunds.

If you have any questions or need help bulk refunding orders in WooCommerce get in touch, or ping Grow Development on Twitter!