Technology is great, isn’t it?

One of the greatest things about modern web technology is the ability to connect different applications to one another via APIs. Specifically, the WooCommerce API enables you to create new orders, manage existing orders, build reports, add products, update products, and so much more.

[clickToTweet tweet=”One of the greatest things about modern web technology is the ability to connect different applications to one another via APIs #WooCommerce” quote=”One of the greatest things about modern web technology is the ability to connect different applications to one another via APIs.”]

In this tutorial, I’ll show you how you can connect your PHP based web app to a WooCommerce store via the Rest API. I’ll start by outlining some of the awesome things you can do with the WooCommerce API and then and then proceed to guiding you through the authentication process. All of this will be topped off with some useful links for further reading.

Buckle up!

WooCommerce API Overview

The best place to go to find out what’s possible with the WooCommerce API is the official docs site. Once connected, you’re able to manage pretty much every aspect of your store.

In this tutorial, I’m going to guide you through the following API interactions:

  • Orders
    • Pull in and display a paginated list of orders.
    • Mark an order as complete or processing.
  • Customers
    • Pull in and display a paginated list of customers.
    • Edit a customer’s name and email address.
    • Delete a customer.
  • Products
    • Pull in and display a paginated list of products.
    • Edit a product’s name, regular price, and sale price.
    • Delete a product.

The PHP App

WooCommerce connect to the API form

For the purposes of this article, I have built a very basic PHP application. The application includes the following:

  • PHP and config files
  • A MySQL database
  • Composer
  • Bootstrap

The flow of the application is simple:

  • Connect a WooCommerce Store.
  • Assign the API keys for the connected store to a user in the database.
  • Once connected, allow the user to view and update:
    • Orders
    • Customers
    • Products

The purpose of this simple PHP application is to introduce you to the WooCommerce API and its capabilities. It should be seen as an insight into how you could integrate the WooCommerce API into your own application.

The app (https://iconic-app.local) and the WooCommerce store (https://iconic-woo.local) I use in this example are both running on my local machine (Mac) using Local by Flywheel.

For simplicity, the app has no log in process and always assumes the local user ID is 1.

Authenticating the Application

The first thing we want to do is connect our existing WooCommerce store to the application. There’s a couple of ways to do this:

  • Generate the API keys manually on your WooCommerce store and enter them into the application.
  • Generate the API keys dynamically via the built-in app authentication endpoint in WooCommerce.

The method I’ll be explaining is the latter.

Enable the WooCommerce API

It’s important to check that the API is actually enabled before you start.

  1. Navigate to WooCommerce > Settings > API.
  2. Make sure Enable REST API is checked.
    WooCommerce enable API
  3. Click Save changes, if you made any.

Create a URL Submission Form

On the dashboard of the app, in my case index.php, I will add a form to enter your WooCommerce store’s URL (as seen in the screenshot above).

<form action="http://iconic-app.local/connect.php" method="post">
	<div class="form-group">
		<label for="store_url">Store URL</label>
		<input type="url" class="form-control" id="store_url" name="store_url" placeholder="E.g. https://example.com/">
	</div>
	<input type="hidden" name="token" value="<?php echo iconic_generate_form_token( 'jck-connect' ); ?>">
	<button type="submit" class="btn btn-default" name="jck-connect">Connect</button>
</form>

The form is simple:

  • It posts to a file named connect.php. This is where we’ll sanitise and validate the submitted URL, then redirect to the store’s app authentication page. If anything went wrong, we’ll display an error back on the dashboard.
  • There is one visible input field, store_url.
  • There is a hidden input field which acts as a nonce.
  • There is a submit button.

The nonce is generated using a function named iconic_generate_form_token().

/**
 * Generate unique form token.
 *
 * @param string $form
 *
 * @return string
 */
function iconic_generate_form_token( $form ) {
	// generate a token from an unique value
	$token = md5( uniqid( microtime(), true ) );

	// Write the generated token to the session variable to check it against the hidden field when the form is sent
	$_SESSION[ $form . '_token' ] = $token;

	return $token;
}

The function accepts a single parameter, $form, This is a text string that identifies the form in use. A token is generated and assigned to the session for checking on submission.

Process the Form Submission

As mentioned, the form posts to a file named connect.php. Inside that file there is a few things going on.

<?php

require_once( $_SERVER['DOCUMENT_ROOT'] . '/inc/setup.php' );

if ( ! iconic_verify_form_token( 'jck-connect' ) ) {
	iconic_add_notice( 'Could not verify form token.' );
	header( "Location: " . iconic_get_app_url() );
	die();
}

$url = filter_input( INPUT_POST, 'store_url', FILTER_SANITIZE_URL );

if ( empty( $url ) ) {
	iconic_add_notice( 'The URL was invalid.' );
	header( "Location: " . iconic_get_app_url() );
	die();
}

$url = iconic_add_trailing_slash( $url );
$auth_url = $url . iconic_get_authorize_path();
$auth_url = iconic_add_auth_params( $auth_url );

if ( ! iconic_url_exists( $auth_url ) ) {
	iconic_add_notice( 'The URL was invalid.' );
	header( "Location: " . iconic_get_app_url() );
	die();
}

$insert_store_url = iconic_db_insert_store_url( $url );

if ( ! $insert_store_url ) {
	iconic_add_notice( 'The connection failed, please try again.', 'warning' );
	header( "Location: " . iconic_get_app_url() );
	die();
}

header( "Location: " . $auth_url );
die();

I start by including my setup.php file. This file includes all of my functions and also starts a session for the nonces and displaying notices. It is included throughout my application.

The nonce is then checked using iconic_verify_form_token(). Let’s take a look at what that function does.

/**
 * Verify form token.
 *
 * @param string $form
 *
 * @return bool
 */
function iconic_verify_form_token( $form ) {
	// check if a session is started and a token is transmitted, if not return an error
	if ( ! isset( $_SESSION[ $form . '_token' ] ) ) {
		return false;
	}

	// check if the form is sent with token in it
	if ( ! isset( $_POST['token'] ) ) {
		return false;
	}

	// compare the tokens against each other if they are still the same
	if ( $_SESSION[ $form . '_token' ] !== $_POST['token'] ) {
		return false;
	}

	return true;
}

The function simply returns a boolean value (true/false) based on the following:

  • If the session variable for the requested nonce is not set at all, return false.
  • If the token was not posted by the form, return false.
  • If the session variable does not equal the posted value, return false.
  • If all of the above passed, return true. The nonce was validated.

Going back to the connect.php file, if the nonce check fails a notice is added and we’re redirected back to the dashboard.

The next step is to assign the user-inputted URL to a variable named $url. I’m using the filter_input() function, which is native to PHP, to sanitise the input as a URL. You’ll see me use this function in a number of places. It’s looking for a posted value with a key of store_url.

If $url is empty (i.e. it has no value, is false, or is null), then we add a notice to the session and redirect to the dashboard where the notice will be displayed.

If not, we proceed to the next step.

Here I’m sanitising the URL further to ensure it has a trailing slash. This will make it easier to work with later down the line.

I then append the authorisation path using iconic_get_authorize_path(). This function just returns a string:

/**
 * Get WooCommerce authorize path.
 *
 * @return string
 */
function iconic_get_authorize_path() {
	return 'wc-auth/v1/authorize';
}

This will then produce a URL which looks like this https://iconic-woo.local/wc-auth/v1/authorize. This is the authorisation URI for any WooCommerce store. However, we also need to add some query string parameters to that to inform the store about the app we’re attempting to connect. Those parameters are:

  • app_name
    This is the name of the PHP app.
  • scope
    These are the permissions we want the API user to have. The possible values are read, write, and read_write.
  • user_id
    This is the user ID from the PHP app. The WooCommerce store will ensure you’ve logged in to assign the API keys to a store user.
  • return_url
    Once the authorisation is confirmed, you will be redirected here.
  • callback_url
    WooCommerce will send the API keys to this URL in JSON format. This URL must be https and it is here where we want to save the API keys to our PHP app user.

To add these parameters in connect.php, I am running the $url through a function named iconic_add_auth_params().

/**
 * Add auth params to URL.
 *
 * @param string $url
 *
 * @return string
 */
function iconic_add_auth_params( $url ) {
	$params = array(
		'app_name'     => 'WooCommerce App',
		'scope'        => 'read_write', // 'read', 'write', 'read_write'
		'user_id'      => iconic_get_user_id(), // Local user ID
		'return_url'   => 'https://iconic-app.local/',
		'callback_url' => 'https://iconic-app.local/callback.php', // Must be https
	);

	// Add PHP_QUERY_RFC3986 so spaces are encoded as %20 and not +
	$query = http_build_query( $params, null, '&', PHP_QUERY_RFC3986 );

	return $url . '?' . $query;
}

I’ve set up an array of the data required. I’m then using http_build_query() to convert the array into the query string format. Finally, I append it to the URL.


Developer Note

If your callback URL is a local domain, you’ll need to add a filter to the WooCommerce store to allow unsafe URLs. Once testing is complete, you can remove this filter.

/**
 * Modify http request args.
 * 
 * @param array  $r   An array of HTTP request arguments.
 * @param string $url The request URL.
 *
 * @return array
 */
function iconic_http_request( $r, $url ) {
	if ( strpos( $url, 'iconic-app.local' ) === false ) {
		return $r;
	}
	
	$r['reject_unsafe_urls'] = false; // Allow unsafe callback_url (localhost)
	$r['sslverify']          = false; // Allow self-signed cert

	return $r;
}

add_filter( 'http_request_args', 'iconic_http_request', 10, 2 );

As this URL must be https and resides on my local machine, I’ve also set the store to accept self-signed SSL certificates.


Developer Note

On top of the above, if you’re using Local by Flywheel you may see a 443 error. If so, you will need to add a line to your hosts file:

  1. Right-click on the WooCommerce site in Local by Flywheel and go to Open Site SSH.
  2. Enter /sbin/ip route|awk '/default/ { print $3 }' and copy the displayed IP.
  3. Type nano /etc/hosts and add a new line at the bottom for the site you’re trying to access (the app). It should look something like 172.17.0.1 iconic-app.local. Use the IP from above and your PHP app’s URL.
  4. If you still see a 443 error, try repeating the above steps but instead add an iconic-woo.local reference to your PHP app’s hosts file.
  5. Note: This may be cleared if the server is restarted and will need to be added again.

Going back to connect.php again, our authorisation URL is ready.

We run it through iconic_url_exists() to ensure the URL exists. If it doesn’t, we add a notice and redirect to the dashboard.

If it does exist, we insert the URL into our database and assign it to the user using iconic_db_insert_store_url(). We’ll need the URL and the API keys in order to connect to the API later on. I won’t go into detail regarding my database interaction methods as it’ll vary between application. The important thing to note is that I saved the URL to the current user.

If this fails, we add a notice and redirect to the dashboard.

Once all validation has passed, we can safely redirect to the WooCommerce authorisation page.

WooCommerce API authentication

Great! Now before we can click Approve we need to set up our callback URL. Remember earlier we set it to https://iconic-app.local/callback.php?

This means in our PHP app we need to create a callback.php file which saves the keys sent by the WooCommerce store.

Create a Callback Handler for the WooCommerce API Authorisation

When you click Approve, this is what happens:

  • WooCommerce sends a JSON response to your callback url, containing the following information:
    • key_id
      This is the ID of your keys in WooCommerce.
    • user_id
      This is your app’s user ID, not the ID of the WooCommerce user.
    • consumer_key
      this is the API consumer key.
    • consumer_secret
      This is the API consumer secret.
    • key_permissions
      This confirms the permissions given to the key combination.
  • At this point, we want to insert the consumer key and secret to the database and assign them to our user.
  • If the callback request was successful, WooCommerce will proceed to send you to the given redirect URL.

The callback.php file looks like this:

<?php

$post_data = file_get_contents("php://input");

if ( empty( $post_data ) ) {
	http_response_code( 400 );
	die;
}

$post_data = json_decode( $post_data );

if( empty( $post_data->key_id ) ) {
	http_response_code( 400 );
	die;
}

require_once( $_SERVER['DOCUMENT_ROOT'] . '/inc/setup.php' );

$user_id = (int) filter_var( $post_data->user_id, FILTER_SANITIZE_NUMBER_INT );
$consumer_key = filter_var( $post_data->consumer_key, FILTER_SANITIZE_STRING );
$consumer_secret = filter_var( $post_data->consumer_secret, FILTER_SANITIZE_STRING );

$insert_user_keys = iconic_db_insert_user_keys( $user_id, $consumer_key, $consumer_secret );

if ( ! $insert_user_keys ) {
	http_response_code( 500 );
	die;
}

WooCommerce will send the API Keys in JSON format to the callback_url, so it’s important to remember that some languages such as PHP will not display it inside the $_POST global variable. In PHP you can access it using $HTTP_RAW_POST_DATA (for old PHP versions) or file_get_contents('php://input');.

Firstly I assign the JSON content to a $post_data variable. If this is empty, I set a HTTP response code of 400 and kill the script. Codes in the 400 range imply this was a client error. You could do some further logging of this error in your app to help with debugging any issues.

If the script dies, WooCommerce will handle the response code. If it isn’t a 200 (success) response then a relevant error will be displayed by WooCommerce.

I then proceed to convert the JSON string into a PHP object using json_decode().

We know the data should contain a key_id parameter, so I check whether that exists. If it doesn’t I throw a 400 response again.

If all our data is present, I’m including my setup.php file and then using filter_var() to sanitise the user_id, consumer_key and consumer_secret.

I can then attempt to insert these into the database and assign them to my user.


Developer Note

There’s no need to encrypt or hash the keys when you insert them into the database. Instead, focus on securing your overall app to avoid any vulnerabilities. You could encrypt the key or secret for extra piece of mind, but if a hacker gets access to your complete server then they could easily decrypt it.


If there was an issue doing this, I throw a 500 response code, indicating there was a server error. Otherwise, the script is complete and a 200 response code will be given.

WooCommerce will then redirect us back to our app.

Instead of the store URL form from before, my app now shows a message about the connected store. It’s also revealed some additional navigation items.

WooCommerce app dashboard

Clicking Disconnect will simply delete the keys from the database and the form will be visible again.

Our app is officially connected to the WooCommerce store. Now we need to use the given keys to connect to the API and start pulling in some data.

Connect to the WooCommerce API Using the Official PHP Library

The next step for our PHP app is load in the official PHP library. This will allow us to use the keys we saved earlier to connect to the WooCommerce store and interact with the available data.

I’m going to load this library into my app using composer. I just need run composer require automattic/woocommerce and the files will be pulled into my local project.

I’ll then include the composer autoloader in my setup.php file, which will give me access to all of the classes available in the library without me having to load them all in manually.

Initiate the API Connection

The first thing we want to do is create a function that pulls our API keys from the database and connects us to the store’s API. Ideally, we only want this to occur once, so we’ll make use of a static variable.

<?php

use Automattic\WooCommerce\HttpClient\HttpClientException;
use Automattic\WooCommerce\Client;

/**
 * Connect to the WooCommerce API.
 *
 * @return \Automattic\WooCommerce\Client|bool
 */
function iconic_api_connect() {
	static $connection;

	if ( isset( $connection ) ) {
		return $connection;
	}

	$keys = iconic_get_user_keys();

	if ( ! $keys ) {
		$connection = false;

		return $connection;
	}

	$connection = new Client(
		$keys['store_url'],
		$keys['consumer_key'],
		$keys['consumer_secret'],
		array(
			'wp_api'     => true,
			'version'    => 'wc/v2',
			'verify_ssl' => false, // Allow self-signed certificates (remove for prod)
		)
	);

	return $connection;
}

All of my WooCommerce API based functions are contained within the same woocommerce.php file. As the PHP library uses namespaces, the first couple of lines are telling my file to use an alias for readability.

Whenever I want to make a request to the API, I’ll call the function iconic_api_connect(). This function starts by announcing a static variable of $connection. If this variable is set, we return it straight away; this means we only need to connect to the API once across all of our requests.

If it isn’t set, we’ll proceed to making our connection request.

Firstly, we get the keys from the database. The function iconic_get_user_keys() will return an array like so:

array(
	'consumer_key'    => 'ck_***************',
	'consumer_secret' => 'cs_***************',
	'store_url'       => 'https://iconic-woo.local/',
)

If the keys were not found, we set $connection to false and return it.

If they were, we initiate a new Client instance. This accepts 4 parameters:

  • $url
    The URL of the WooCommerce store.
  • $consumer_key
    The user’s API consumer key.
  • $consumer_secret
    The user’s API consumer secret.
  • $options
    An array of options.

We pass in the first parameters as expected. We’re then passing in a few options:

  • Firstly, we’re setting wp_api to true. This allows us to make requests to the new WP REST API integration (WooCommerce 2.6 or later).
  • Then we’re using version 2 of the API.
  • Finally, we’re telling the library not to verify SSL certificates. This is only for the local environment as self-signed certificates will fail.

The Client instance is then returned. If there were any errors connecting, these will become apparent when we attempt to make some API requests. Otherwise, we should now have a working API connection.

Get a Paginated List of All Orders

A lot of the calls you make to the API follow the same structure. For example, requesting a “collection” of orders, customers, or products use very similar API calls. As such, I’ve built a few wrapper functions to get a collection, update a single collection item, get a single collection item, and delete a single collection item.

Our goal is to list all of the orders from our store in a table, with a simple button to mark the order as complete or processing. The results will also be paginated.

List all orders via the WooCommerce API

To do this, we need to make a request to the orders endpoint, like so https://iconic-woo.local/wp-json/wc/v2/orders. We’re going to use a function named iconic_api_get_collection().

/**
 * Get collection.
 *
 * @param string $type customers|orders|products
 * @param array  $args
 *
 * @return mixed
 */
function iconic_api_get_collection( $type, $args = array() ) {
	if ( empty( $type ) ) {
		return false;
	}

	static $result = array();

	$args['page']     = isset( $args['page'] ) ? $args['page'] : iconic_get_current_page();
	$args['per_page'] = isset( $args['per_page'] ) ? $args['per_page'] : iconic_get_current_per_page();

	$key = sprintf( '%s-%s', $type, md5( serialize( $args ) ) );

	if ( isset( $result[ $key ] ) ) {
		return $result[ $key ];
	}

	$connection = iconic_api_connect();

	if ( ! $connection ) {
		$result[ $key ] = false;

		return $result[ $key ];
	}

	try {
		$result[ $key ] = $connection->get( $type, $args );
	} catch ( HttpClientException $e ) {
		$result[ $key ] = false;
		iconic_add_notice( $e->getMessage() );
	}

	return $result[ $key ];
}

As mentioned, we’re going to use this function to get results for orders, customers, and products. As such, the function accepts 2 parameters:

  • $type
    This will either be orders, customers, or products. We’ll use this parameter to build up the endpoint URI.
  • $args
    This is an array of arguments for the request. You can view all the available parameters for your request in the docs.

If $type is empty, then we can’t make a request, so we return false.

We’re then going to create a static variable of $result as we only want to make this request once per page request in our app.

In order to handle our pagination, I’m also setting the arguments for page and per_page. If it’s already set as an argument, we’ll use that, otherwise we use iconic_get_current_page() and iconic_get_current_per_page() to pull it from the URL query string.

As we can call iconic_api_get_collection() multiple times with different parameters, we’ll be assigning each request a $key and adding it to the $result static variable as an array item.

To create our $key I’m combining the request $type with a hashed version of the $args array. This will create a unique string like so orders-40cd750bba9870f18aada2478b24840a, based on the arguments of our request.

Next we check if the $key has already been added to the static $result variable. If it has, we return it.

If not, we proceed to make the request.

We’re going to create an API instance using the iconic_api_connect() function from earlier and assign it to a $connection variable.

If that connection fails, we’ll add our $key to the $result array with a value of false.

We then attempt to make the request using a try/catch block. If the contents contained within try throw an error, then the contents of catch will be executed.

To make the request, we’re using the get() method from the WooCommerce PHP library. The method accepts 2 parameters:

  • $endpoint
    This is the API endpoint. I.e. anything after the initial URL https://iconic-woo.local/wp-json/wc/v2/. In this case it’ll be orders.
  • $args
    As per our own function, this is an array of arguments for the endpoint.

The WooCommerce API PHP Library has a few methods like the get() method. These are:

  • get()
    For making GET requests.
  • post()
    For making POST requests.
  • put()
    For making PUT requests.
  • delete()
    For making DELETE requests.
  • options()
    For making OPTIONS requests.

The type of request you need is outlined in the API docs:

WooCommerce API request type

If all is well, the collection items will be returned. If not, we add a notice to display the error received and set the results to false.

Now that we have a function to get a collection from WooCommerce, we’ll want to use it. I’ve created an orders.php page in the app. On this page, I simply request the orders like so:

<?php $orders = iconic_api_get_collection( 'orders' ); ?>

If the result of that request is empty, I display a notice:

WooCommerce API no orders found

However, if the request was successful, then we’ll have an array of orders to loop through.

<table class="table">
	<thead>
		<tr>
			<th scope="col">ID</th>
			<th scope="col">Status</th>
			<th scope="col">Customer ID</th>
			<th scope="col">Items</th>
			<th scope="col">Total</th>
			<th scope="col">Actions</th>
		</tr>
	</thead>
	<tbody>
		<?php foreach ( $orders as $order ) { ?>
			<tr>
				<th scope="row"><?php echo $order->id; ?></th>
				<td><?php echo iconic_get_status_badge( $order->status ); ?></td>
				<td><?php echo $order->customer_id; ?></td>
				<td><?php echo count( $order->line_items ); ?></td>
				<td><?php echo $order->total; ?><?php echo $order->currency; ?></td>
				<td>
					<?php if ( $order->status === 'completed' ) { ?>
						<a href="<?php echo iconic_get_current_url( array(
							'action'   => 'order_status',
							'status'   => 'processing',
							'order_id' => $order->id,
						) ); ?>" class="btn btn-default">Mark Processing</a>
					<?php } else { ?>
						<a href="<?php echo iconic_get_current_url( array(
							'action'   => 'order_status',
							'status'   => 'completed',
							'order_id' => $order->id,
						) ); ?>" class="btn btn-default">Mark Complete</a>
					<?php } ?>
				</td>
			</tr>
		<?php } ?>
	</tbody>
</table>

I’ll be generating a table which will display the order IDStatusCustomer ID, Items, Total, and Actions.

Each item in the $orders array contains an order object. If you refer back to the API docs again, it will indicate the parameters available in each order object, which you can then display in your app. Look for the “JSON response example” heading.

WooCommerce API docs

To paginate these results I created a pagination function named iconic_display_pagination_links().

This function makes another API request to the next page to see if any results are returned. If they are, we know there’s another page available. The same concept is repeated for the “previous” pagination link.

Update an Order Status via the WooCommerce API

In my orders loop, you may have noticed two buttons in the Actions column. If the order’s status is completed then I show a button to mark it as processing. If it’s processing I show a button to mark it as completed.

<?php if ( $order->status === 'completed' ) { ?>
	<a href="<?php echo iconic_get_current_url( array(
		'action'   => 'order_status',
		'status'   => 'processing',
		'order_id' => $order->id,
	) ); ?>" class="btn btn-default">Mark Processing</a>
<?php } else { ?>
	<a href="<?php echo iconic_get_current_url( array(
		'action'   => 'order_status',
		'status'   => 'completed',
		'order_id' => $order->id,
	) ); ?>" class="btn btn-default">Mark Complete</a>
<?php } ?>

The href of these buttons is generated using my iconic_get_current_url() function. This function simply gets the current URL, including the query string, and appends or replaces query string parameters with new arguments.

For both of these buttons I’m setting a query string parameter of action=order_status. In my app, if the action parameter exists then I process it and redirect upon completion. I do this by running the function iconic_process_action() on all pages.

This “listens” for the action parameter and runs a function based on the value.

/**
 * Process actions.
 */
function iconic_process_actions() {
	$action = filter_input( INPUT_GET, 'action' );

	if ( ! $action ) {
		return;
	}

	$action_name = sprintf( 'iconic_action_%s', $action );

	if ( ! function_exists( $action_name ) ) {
		return;
	}

	$response = call_user_func( $action_name );

	header( "Location: " . $response );
	die();
}

Firstly it gets and sanitises the action parameter. If it does not exist, the function returns and does nothing else.

If it does exist, then I use the value of it to generate a function name. In the order_status example, a function name of iconic_action_order_status will be generated.

If this function does not exist, the function returns again and does nothing.

If the function does exist, we use call_user_func() to make a call to it; this allows us to run functions or methods with variable names.

All of my action functions return a redirect URL, so when we get a response from the action we’re redirected to that page.

Let’s take a look at what the iconic_action_order_status() function does.

/**
 * Update order status.
 */
function iconic_action_order_status() {
	$redirect = iconic_get_current_url( array(
		'action'   => null,
		'order_id' => null,
		'status'   => null,
	) );
	$order_id = (int) filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT );
	$status   = filter_input( INPUT_GET, 'status' );

	if ( ! $order_id || ! $status ) {
		iconic_add_notice( 'Order status could not be updated.' );

		return $redirect;
	}

	$update_status = iconic_api_update_collection_item( 'orders', $order_id, array(
		'status' => $status,
	) );

	if ( ! $update_status ) {
		iconic_add_notice( 'Order status could not be updated.' );

		return $redirect;
	}

	iconic_add_notice( sprintf( 'Order #%d status updated.', $order_id ), 'success' );

	return $redirect;
}

All going well, this function is going to make an API request to change the status of an order.

Firstly, I’m setting the redirect URL using my iconic_get_current_url() function. By setting the values to null it will remove them completely from the URL; we don’t want to run the action again when we redirect.

I then grab the order_id and status from the request and sanitise the input accordingly.

If either of them are not valid, I add a notice and return my redirect URL.

Otherwise, I proceed to making the API request using a function named iconic_api_update_collection_item(). As before, this function can be used for orders, products, or customers.

/**
 * Update collection item.
 *
 * @param string $type customers|orders|products
 * @param int    $id
 * @param array  $args
 *
 * @return array|bool
 */
function iconic_api_update_collection_item( $type, $id, $args = array() ) {
	$connection = iconic_api_connect();

	if ( ! $connection || empty( $id ) || empty( $type ) ) {
		return false;
	}

	$request = sprintf( '%s/%d', $type, $id );

	try {
		$result = $connection->put( $request, $args );
	} catch ( HttpClientException $e ) {
		$result = false;
		iconic_add_notice( $e->getMessage() );
	}

	return $result;
}

The structure of this function is similar to the iconic_get_collection() function.

The function accepts 3 parameters:

  • $type
    This is the collection type, i.e. orders, customers, or products.
  • $id
    This is the ID of the specific collection item. In this instance our order ID.
  • $args
    This is an array of request arguments.

The function starts by getting the API instance.

If $connection is false, $id is empty, or $type is empty, then we return false.

Next we build our API endpoint and assign it to a $request variable. The endpoint is in the form of {collection_type}/{collection_item_id}. For example, orders/22.

This time we’re using the put() method from the WooCommerce API PHP library. We can still use a try/catch block for the request and handle it in the same way.

To clarify, the order ID is now part of the request URL, and we’re passing a status parameter in the $args array.

Going back to the iconic_action_order_status() function, if the update status request was not successful, then we add an error notice and redirect.

If it was successful, we add a success notice and redirect back.

It really is that simple. We can now reuse this function for a collection item update request.

Other API Requests for Orders

There’s so much more you could do with the API in relation to your orders. You can:

  • Use the data to build your own reports (or use the reports endpoint).
  • Create new orders.
  • Update any information for an existing order.
  • Build an app for your warehouse based on certain parameters, so they can see what needs to be shipped out/created.

If you do try anything out, let me know in the comments.

Get a Paginated List of All Customers

Now that we’ve built out the functions to get a collection of items, we can quite easily list all of the store’s customers.

List all customers via the WooCommerce API

I’ve created a customers.php page in the app, and then simply requested the following:

$customers = iconic_api_get_collection( 'customers', array(
	'orderby' => 'id',
) );

If customers are found, then I loop through them and output the data I want to display. You can see in the screenshot that I’ve also added to actions to each customer, Edit and Delete.

Edit will open up a new page, edit-customer.php, with a form for us to edit the customer’s email, first name, and last name. The customer ID is passed via URL parameters.

Delete creates a request to be handled with the iconic_process_actions() function. Let’s take a look at the delete action first.

Delete a Customer Using the WooCommerce API

The Delete button links to a URL that looks like this https://iconic-app.local/customers.php?action=delete_collection_item&type=customers&id=2. You can see we’re passing 3 parameters:

  • action=delete_collection_item
    This is going to trigger a function named iconic_action_delete_collection_item().
  • type=customers
    This is the type of collection item we’re attempting to delete.
  • id=2
    This is the ID of our collection item, or in this case, customer.

If we take a look inside the iconic_action_delete_collection_item() function we can see what’s going on:

/**
 * Delete collection item.
 */
function iconic_action_delete_collection_item() {
	$redirect = iconic_get_current_url( array(
		'action' => null,
		'type'   => null,
		'id'     => null,
	) );
	$id       = (int) filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
	$type     = filter_input( INPUT_GET, 'type' );

	if ( ! $id || ! $type ) {
		iconic_add_notice( 'Item could not be deleted.' );

		return $redirect;
	}

	$delete_item = iconic_api_delete_collection_item( $type, $id );

	if ( ! $delete_item ) {
		return $redirect;
	}

	iconic_add_notice( sprintf( 'Item #%d deleted.', $id ), 'success' );

	return $redirect;
}

As per our other action, we start by preparing a “nulled” redirect URL. Like I said before, any null parameters will be stripped from the current URL.

We then fetch the id and type from the URL, and sanitise accordingly.

If either of these are false, we add a notice and return the redirect URL.

If not, we attempt to delete the collection item using iconic_api_delete_collection_item().

/**
 * Delete collection item.
 *
 * @param string $type customers|orders|products
 * @param int    $id
 * @param array  $args
 *
 * @return array|bool
 */
function iconic_api_delete_collection_item( $type, $id, $args = array() ) {
	$connection = iconic_api_connect();

	if ( ! $connection || empty( $id ) || empty( $type ) ) {
		return false;
	}

	$request       = sprintf( '%s/%d', $type, $id );
	$args['force'] = isset( $args['force'] ) ? $args['force'] : true;

	try {
		$result = $connection->delete( $request, $args );
	} catch ( HttpClientException $e ) {
		$result = false;
		iconic_add_notice( $e->getMessage() );
	}

	return $result;
}

This function accepts 3 parameters:

  • $type
    The type of collection we want to delete an item from. I.e. orders, customers, or products.
  • $id
    The ID of the item we want to delete.
  • $args
    An array of arguments for the API request.

It’s structured much like our other API request functions. We first fetch the API connection. If all parameters are set correctly we then formulate the $request endpoint; in the case of a delete request, the endpoint is {collection_type}/{collection_item_id}. So for deleting a customer this would look something like customers/3.

We then set the force argument to true by default, in order to permanently delete the item.

We attempt the request in a try/catch block using the delete() method of the WooCommerce API PHP library, and return the result. On success, the deleted collection item will be returned.

Back in our action we either add an error or success notice, depending on the result, then return the redirect URL.

Edit a Customer Using the WooCommerce API

The Edit links in the customer list go to a page like this https://iconic-app.local/edit-customer.php?id=2.

On this page I have a simple form with 3 fields; email, first name, and last name. Those fields are populated with the customer’s existing data.

Edit a customer via the WooCommerce API

To do this, we need to first fetch the customer ID from the URL, and then make a request to the API to get a single collection item.

$customer_id = (int) filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
$customer = iconic_api_get_collection_item( 'customers', $customer_id );

The iconic_api_get_collection_item() function is straightforward.

/**
 * Get collection item.
 *
 * @param string $type customers|orders|products
 * @param int    $id
 * @param array  $args
 *
 * @return array|bool
 */
function iconic_api_get_collection_item( $type, $id, $args = array() ) {
	$connection = iconic_api_connect();

	if ( ! $connection || empty( $id ) || empty( $type ) ) {
		return false;
	}

	$request = sprintf( '%s/%d', $type, $id );

	try {
		$result = $connection->get( $request, $args );
	} catch ( HttpClientException $e ) {
		$result = false;
		iconic_add_notice( $e->getMessage() );
	}

	return $result;
}

As with the other API functions, we start by fetching our API connection. If it fails, or there is no $id or $type set, we return false.

Otherwise we build our request endpoint. For fetching a collection item, the endpoint looks like this {collection_item_type}/{collection_item_id}, so for a customer it’d be customers/3.

We then use the WooCommerce API PHP library to make a get() request with no args; all going well it will return a customer object. Otherwise, we’ll return false and add an error notice.

Back to our edit-customer.php page.

If a customer exists, then we can easily grab their email, first name, and last name, and use it as the default value for our form fields.

echo $customer->email;
echo $customer->first_name;
echo $customer->last_name;

When submitted, the form will send a post request to the current page, along with an action parameter of update_customer.

This means it will trigger one of our action functions, specifically iconic_action_update_customer().

/**
 * Update customer.
 */
function iconic_action_update_customer() {
	$redirect    = iconic_get_current_url( array(
		'action' => null,
	) );
	$customer_id = (int) filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
	$email       = filter_input( INPUT_POST, 'email-address' );
	$first_name  = filter_input( INPUT_POST, 'first-name' );
	$last_name   = filter_input( INPUT_POST, 'last-name' );

	if ( ! $customer_id ) {
		iconic_add_notice( 'No customer ID was given.' );
		
		return $redirect;
	}

	$update_customer = iconic_api_update_collection_item( 'customers', $customer_id, array(
		'email'      => $email,
		'first_name' => $first_name,
		'last_name'  => $last_name,
	) );

	if ( ! $update_customer ) {
		iconic_add_notice( 'Sorry, the customer could not be updated at this time.' );
		
		return $redirect;
	}

	iconic_add_notice( 'Customer updated.', 'success' );

	return $redirect;
}

As with the other action functions, we start by stripping the action parameter from the redirect URL.

We then fetch the customer ID, and the contents of the form fields; email-address, first-name, and last-name. It’s important to note that I’m referencing INPUT_POST in the filter_input() function for the form field values, as these are not submitted via a URL query string.

If the $customer_id is empty, we redirect and add an error notice. Otherwise, we proceed to update the customer.

Just like the order status update function we used earlier, we’re using iconic_api_update_collection_item() to update the customer. We can simply pass the form field values as args for the API.

If it fails, we add a notice and redirect. If not, we add a success notice and redirect.

When we’re successfully redirected, the new data will be shown.

Get a Paginated List of All Products

At this point, we’ve got all the functions required to get a collection products, update a product, and delete a product.

As such, I’m going to start by listing all of the store’s products.

List all products via the WooCommerce API

To fetch the products from WooCommerce we can do the following:

<?php $products = iconic_api_get_collection( 'products' ); ?>

If products are returned I can then loop through them and display any data I want.

<?php foreach ( $products as $product ) { ?>
	<tr>
		<th scope="row"><?php echo $product->id; ?></th>
		<td><?php echo $product->name; ?></td>
		<td class="text-capitalize"><?php echo $product->type; ?></td>
		<td><?php echo ! empty( $product->price_html ) ? $product->price_html : "&mdash;"; ?></td>
		<td><?php echo $product->status; ?></td>
		<td><?php echo $product->catalog_visibility; ?></td>
		<td>
			<a href="/edit-product.php?id=<?php echo $product->id; ?>" class="btn btn-default">Edit</a>
			<a href="<?php echo iconic_get_current_url( array(
				'action' => 'delete_collection_item',
				'type'   => 'products',
				'id'     => $product->id,
			) ); ?>" class="btn btn-default">Delete</a>
		</td>
	</tr>
<?php } ?>

I’m using an Edit and Delete button, like I did with the customer list.

Delete uses the exact same process as deleting a customer. We run the delete_collection_item action, but pass in a type of products.

Edit is similar to the edit-customer.php template, too. However, in the case of a product we’re going to be able to update the product title, regular price, and sale price.

I’ve created an edit-product.php template which I link to along with the product ID.

Edit product via the WooCommerce API

As before we fetch the product’s existing data using iconic_api_get_collection_item().

$product_id = (int) filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
$product = iconic_api_get_collection_item( 'products', $product_id );

If a product is found, we can then fetch the existing data:

echo $product->name;
echo $product->regular_price;
echo $product->sale_price;

This form posts with an action of update_product which, as we know, will call the iconic_action_update_product() function.

/**
 * Update product.
 */
function iconic_action_update_product() {
	$redirect      = iconic_get_current_url( array(
		'action' => null,
	) );
	$product_id    = (int) filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
	$name          = filter_input( INPUT_POST, 'name' );
	$regular_price = filter_input( INPUT_POST, 'regular-price', FILTER_SANITIZE_NUMBER_INT );
	$sale_price    = filter_input( INPUT_POST, 'sale-price', FILTER_SANITIZE_NUMBER_INT );

	if ( ! $product_id ) {
		iconic_add_notice( 'No product ID was given.' );
		
		return $redirect;
	}

	$update_product = iconic_api_update_collection_item( 'products', $product_id, array(
		'name'          => $name,
		'regular_price' => $regular_price,
		'sale_price'    => $sale_price,
	) );

	if ( ! $update_product ) {
		iconic_add_notice( 'Sorry, the product could not be updated.' );
		
		return $redirect;
	}

	iconic_add_notice( 'Product updated.', 'success' );

	return $redirect;
}

In this function, we prepare our redirect URL by stripping out the action parameter. We then collect the $product_id, $name, $regular_price, and $sale_price.

As long as we have a $product_id we proceed to run the iconic_api_update_collection_item() function.

We’re calling the products endpoint, passing in our $product_id, and finally passing an array of API arguments with our posted values.

As normal, we either add an error or success notice and return our redirect URL.

Conclusion

I hope this tutorial provides a good starting point for you to get going with the WooCommerce API using the official PHP library. We’ve learnt how to authenticate an app, list all orders, update an order status, list all customers, edit or delete a customer, list all products, and edit or delete a product.

There’s so much more you can achieve with the API, and interacting with it is simple once you’ve built up a set of core functions to do so.

Further Reading

As always, there’s plenty more to learn. Here’s some useful links to get you going: